k我有 shadcn/ui 表单组件,如下所示
"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;
以及我用 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;
所以我想设置多个值而不是一个值(保存多个cloudinary url)
我尝试转发字符串数组,但它不起作用,因为 onChange 函数调用每个 onUpload,我正在使用 Zod 和react-hook-form
我看到的一个问题是 ImageUpload 组件的 onChange 属性应确保状态正确更新,同时保留现有图像。 你可以这样做:
<ImageUpload
onChange={(url) => {
const currentImages = form.getValues("images");
field.onChange([...currentImages, url]);
}}
....
/>
这样,它会将新的 url 添加到状态中的现有图像中