Unable to upload multiple images in Cloudinary

71 Views Asked by At

I want to create a products page in which I can upload multiple images using Cloudinary in next. Here I created a component for uploading image

Image Upload component

"use client";

import { CldUploadWidget } from 'next-cloudinary';
import { useEffect, useState } from 'react';

import { Button } from '@/components/ui/button';
import Image from 'next/image';
import { ImagePlus, Trash } from 'lucide-react';

interface ImageUploadProps {
  disabled?: boolean;
  onChange: (value: string) => void;
  onRemove: (value: string) => void;
  value: string[];
}

const ImageUpload: React.FC<ImageUploadProps> = ({
  disabled,
  onChange,
  onRemove,
  value
}) => {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  
  const onUpload = (result: any) => {
    onChange(result.info.secure_url);
  };
  

  if (!isMounted) {
    return null;
  }

  return ( 
    <div>
      <div className="mb-4 flex items-center gap-4">
        {value.map((url) => (
          <div key={url} className="relative w-[200px] h-[200px] rounded-md overflow-hidden">
            <div className="z-10 absolute top-2 right-2">
              <Button type="button" onClick={() => onRemove(url)} variant="destructive" size="sm">
                <Trash className="h-4 w-4" />
              </Button>
            </div>
            <Image
              fill
              sizes=''
              className="object-cover"
              alt="Image"
              src={url}
            />
          </div>
          
        ))
        }
      
      </div>
      <CldUploadWidget onSuccess={onUpload} uploadPreset="ox48luzl">
        {({ open }) => {
          const onClick = () => {
            open();
          };

          return (
            <Button 
              type="button" 
              disabled={disabled} 
              variant="secondary" 
              onClick={onClick}
            >
              <ImagePlus className="h-4 w-4 mr-2" />
              Upload an Image
            </Button>
          );
        }}
      </CldUploadWidget>
    </div>
  );
}
 
export default ImageUpload;

and

Product Form page

where I call my image upload

'use client'

import { Button } from "@/components/ui/button"
import { Heading } from "@/components/ui/heading"
import { Product, Image, Category } from "@prisma/client";
import { Trash } from "lucide-react"
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form";
import * as z from "zod"
import { AlertModal } from "@/components/modals/alert-model";
import axios from "axios";
import toast from "react-hot-toast";
import { Separator } from "@/components/ui/separator";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import ImageUpload from "@/components/ui/image-upload";


const formSchema = z.object({
  name: z.string().min(1),
  promocode: z.string().min(2),
  affiliateLink: z.string().min(1),
  description: z.string().min(1),
  images:z.object({url:z.string()}).array(),
  categoryId: z.string().min(1),
  price: z.coerce.number().min(1),
})

type ProductFormValues = z.infer<typeof formSchema>;

interface ProductFormProps {
  initialData: Product & {
    images: Image[]
  } | null;
  categories: Category[]
};
const ProductForm: React.FC<ProductFormProps> = ({
  initialData,
  categories
}) => {

  const params = useParams();
  const router = useRouter();

  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);

  const title = initialData ? 'Edit Product' : 'Create Product'
  const description = initialData ? 'Edit a Product' : 'Add a new Product';
  const toastMassege = initialData ? 'Product Update' : 'Product Created';
  const action = initialData ? 'Save Changes' : 'Create';

  const defaultValues  = initialData ? {
    ...initialData,
    price: parseFloat(String(initialData?.price)),
    promocode: initialData.promocode || "", 
  } : {
    name: '',
    images:[],
    price:0,
    description: '',
    catogoryId: '',
    promocode: '',
    affiliateLink: '',
  }

  const form = useForm<ProductFormValues>({
    resolver: zodResolver(formSchema),defaultValues
  })

  const onDelete = async () => {
    try {
      setLoading(true)
      await axios.delete(`/api/products/${params.productId}`)
      router.push('/products')
      toast.success('Product Deleted Successfully!')
    } catch (error: any) {
      toast.error('something wen wrong')
    }
    finally {
      setLoading(false)
    }
  }
  return (
    <>
      <AlertModal
        isOpen={open}
        onClose={() => setOpen(true)}
        onConfirm={onDelete}
        loading={loading}
      />
      <div className="flex item-center justify-between">
        <Heading title={title} description={description} />
        {initialData &&(
          <Button
          disabled={loading}
         variant="destructive"
         size="sm"
       >
         <Trash className="h-4 w-4" />
       </Button>
        )}
      </div>
      <Separator/>
      <Form {...form}>
          <form className="space-y-8 w-full">
          <FormField
            control={form.control}
            name="images"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Images</FormLabel>
                <FormControl>
                  <ImageUpload 
                    value={field.value.map((image)=>image.url)} 
                    disabled={loading} 
                    onChange={(url) => field.onChange([...field.value, { url }])}
                    onRemove={(url) => field.onChange([...field.value.filter((current) => current.url !== url)])}
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          /> 
            <FormField 
            control={form.control} 
            name="name"
            render={({field})=>(
              <FormItem>
                <FormLabel>Product Name</FormLabel>
                <FormControl>
                  <Input disabled={loading} placeholder="Enter Product Name" {...field}/>
                </FormControl>
              </FormItem>
            )} />
          
          </form>
      </Form>
    </>
  )
}

export default ProductForm

In this code, almost everything works fine but when trying to upload multiple images in the Cloudinary widget only the first or first uploaded image displays and is stored in the value.

IU wanted to implement an array of image URLs uploaded and stored.

1

There are 1 best solutions below

1
PixelCook On

you'll need to add the multiple parameter to the upload widget.

You can read about it via documentation: https://cloudinary.com/documentation/upload_widget_reference#:~:text=opened.%0ADefault%3A%20local-,multiple,-Boolean