How to upload and store multiple images instead of one image in next-cloudinary and react-hook-form

kI have shadcn/ui Form component look like this

"use client";
import { Fragment } from "react";
import type { NextPage } from "next";
import { useRouter } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { createProject } from "@/actions/actions";
import { Button } from "@/components/ui/button";
import {
    Card,
    CardContent,
    CardDescription,
    CardFooter,
    CardHeader,
    CardTitle,
} from "@/components/ui/card";
import {
    Form,
    FormControl,
    FormDescription,
    FormField,
    FormItem,
    FormLabel,
    FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { useToast } from "@/components/ui/use-toast";
import ImageUpload from "@/components/image-upload";
const formSchema = z.object({
    title: z.string().min(5, {
        message: "Title must be at least 5 characters.",
    }),
    caption: z.string().min(10, {
        message: "Caption must be at least 10 characters.",
    }),
    images: z.array(z.string()),
});

const Page: NextPage = () => {
    const { toast } = useToast();
    const form = useForm<z.infer<typeof formSchema>>({
        resolver: zodResolver(formSchema),
        defaultValues: {
            title: "",
            caption: "",
            images: [],
        },
    });

    const isLoading = form.formState.isSubmitting;
    const router = useRouter();

    return (
        <Fragment>
            <Card className="lg:w-1/2 mx-auto">
                <CardHeader>
                    <CardTitle className="text-base md:text-3xl">
                        Create Project
                    </CardTitle>
                    <CardDescription>
                        Deploy your new project in one-click.
                    </CardDescription>
                </CardHeader>
                <CardContent>
                    <Form {...form}>
                        <form
                            onSubmit={form.handleSubmit(onSubmit)}
                            className="space-y-8"
                        >
                            <FormField
                                control={form.control}
                                name="title"
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel className="font-bold text-base lg:text-3xl">
                                            Title
                                        </FormLabel>
                                        <FormControl>
                                            <Input
                                                placeholder="Title"
                                                {...field}
                                            />
                                        </FormControl>
                                        <FormDescription>
                                            This is the project public title.
                                        </FormDescription>
                                        <FormMessage />
                                    </FormItem>
                                )}
                            />
                            <FormField
                                control={form.control}
                                name="caption"
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel className="font-bold text-base lg:text-3xl">
                                            Caption
                                        </FormLabel>
                                        <FormControl>
                                            <Input
                                                placeholder="Caption"
                                                {...field}
                                            />
                                        </FormControl>
                                        <FormDescription>
                                            This is the project public caption.
                                        </FormDescription>
                                        <FormMessage />
                                    </FormItem>
                                )}
                            />
                            <FormField
                                control={form.control}
                                name="images"
                                render={({ field }) => (
                                    <FormItem className="flex flex-col space-y-5">
                                        <FormLabel className="font-bold text-base lg:text-3xl">
                                            Upload Image
                                        </FormLabel>
                                        <FormControl>
                                            <ImageUpload
                                                disabled={isLoading}
                                                onChange={field.onChange}
                                                value={field.value}
                                            />
                                        </FormControl>
                                        <FormDescription>
                                            This is the project image.
                                        </FormDescription>
                                        <FormMessage />
                                    </FormItem>
                                )}
                            />
                            <CardFooter>
                                <div className="mx-auto w-96 flex flex-col md:justify-between md:flex-row space-y-7 md:space-y-0">
                                    <Button
                                        className="w-block"
                                        size="lg"
                                        onClick={() => router.back()}
                                        variant="outline"
                                    >
                                        Cancel
                                    </Button>
                                    <Button
                                        size="lg"
                                        type="submit"
                                        disabled={isLoading}
                                    >
                                        Submit
                                    </Button>
                                </div>
                            </CardFooter>
                        </form>
                    </Form>
                </CardContent>
            </Card>
        </Fragment>
    );
    async function onSubmit(values: z.infer<typeof formSchema>) {
        await createProject({
            title: values.title,
            caption: values.caption,
            images: values.images,
        });
        form.reset();
        window.location.replace("/dashboard/projects");
        toast({
            variant: "success",
            title: "You successfuly added a new project.",
        });
    }
};

export default Page;

and custom component I have wrote with next-cloudinary

"use client";
import React, { useCallback } from "react";
import Image from "next/image";
import { CldUploadWidget } from "next-cloudinary";
import { ImagePlus } from "lucide-react";

declare global {
    var cloudinary: any;
}

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

const ImageUpload: React.FC<ImageUploadProps> = ({ value, onChange }) => {
    const handleUpload = useCallback(
        (result: any) => {
            onChange(result.info.scure_url);
        },
        [onChange]
    );
    return (
        <CldUploadWidget
            onUpload={handleUpload}
            options={{
                maxFiles: 5,
            }}
            uploadPreset="uxvsxycu"
        >
            {({ open }) => {
                return (
                    <div
                        onClick={() => open?.()}
                        className="p-4 border-4 cursor-pointer border-dashed border-primary rounded-lg hover:opacity-75 transition flex flex-col space-y-2 items-center justify-center"
                    >
                        <div className="relative flex items-center justify-center  lg:h-40 lg:w-96 mx-auto">
                            <div className="flex flex-col space-y-5 justify-center items-center w-full h-full">
                                <ImagePlus size={70} color="gray" />
                                <div className="font-semibold text-lg">
                                    Click to upload
                                </div>
                                {value && (
                                    <div className="absolute inset-0 w-full h-full">
                                        <Image
                                            alt="upload"
                                            src={value}
                                            fill
                                            style={{ objectFit: "cover" }}
                                        />
                                    </div>
                                )}
                            </div>
                        </div>
                    </div>
                );
            }}
        </CldUploadWidget>
    );
};

export default ImageUpload;

So I want to set multiple values instead of one value (saving multiple cloudinary urls)

I tried to forward array of string but it didn’t work since onChange function invoke every onUpload, I’m using Zod and react-hook-form

Leave a Comment