如何用Zod+React Hook Form验证React Dropzone文件?验证失效求助
问题分析与修复
核心问题
你遇到的图片上传验证失效,主要有3个关键错误:
- Zod Schema类型不匹配+缺少必填规则:定义的
images是单个File类型,但实际上传的是File数组,且未添加非空验证 - React Hook Form默认值未初始化:
images字段默认值为空对象,不符合数组类型预期 - FileUploader未同步表单状态:仅维护了预览URL,未跟踪实际文件数组,且缺少删除图片时的表单值更新逻辑
修正后的代码
1. Zod验证Schema
import { z } from "zod"; export const myFormSchema = z.object({ // 改为File数组,且要求至少上传一张图片 images: z.array(z.custom<File>()).nonempty("请至少上传一张图片"), title: z.string().nonempty("标题不能为空"), // 补充标题必填验证 });
2. FileUploader组件(新增文件状态跟踪与删除功能)
import { FileWithPath, useDropzone } from "react-dropzone"; import { useState, useCallback } from "react"; import Image from "next/image"; import { Upload, X } from "lucide-react"; type FileUploaderProps = { files: File[]; onFilesChange: (files: File[]) => void; }; const FileUploader = ({ files, onFilesChange }: FileUploaderProps) => { // 根据传入的文件生成预览URL const fileUrls = files.map(file => URL.createObjectURL(file)); const onDrop = useCallback( (acceptedFiles: FileWithPath[]) => { onFilesChange(acceptedFiles); }, [onFilesChange], ); // 删除指定索引的文件,同步更新表单值 const handleDeleteFile = (index: number) => { const updatedFiles = [...files]; updatedFiles.splice(index, 1); onFilesChange(updatedFiles); }; const { getRootProps, getInputProps } = useDropzone({ onDrop, accept: { "image/*": [".png", ".jpeg", ".jpg", ".svg"], }, maxFiles: 20, // 和标题提示的"最多20张"对应 }); return ( <div {...getRootProps()} className="flex-center bg-dark-3 flex cursor-pointer flex-col rounded-xl p-4" > <input {...getInputProps()} className="cursor-pointer" /> {files.length > 0 ? ( <> <div className="grid grid-cols-3 gap-2"> {fileUrls.map((image, index) => ( <div key={index} className="relative group"> <Image alt="Product image" className="aspect-square w-full rounded-md object-cover" height="84" src={image} width="84" /> {/* 删除按钮,hover时显示 */} <button onClick={(e) => { e.stopPropagation(); // 阻止触发dropzone的点击事件 handleDeleteFile(index); }} className="absolute top-1 right-1 bg-black/70 text-white rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity" > <X className="h-3 w-3" /> </button> </div> ))} </div> <p className="mt-10 text-center">Click or drag photo to replace</p> </> ) : ( <div className="mt-2 grid grid-cols-3 gap-2"> <div className="flex aspect-square w-full items-center justify-center rounded-md border border-dashed shadow"> <Upload className="h-4 w-4 text-muted-foreground" /> <span className="sr-only">Upload</span> </div> </div> )} </div> ); }; export default FileUploader;
3. 表单组件(修正默认值与组件传参)
'use client'; import { z } from 'zod'; import { useForm } from 'react-hook-form'; // 补充缺失的useForm导入 import { zodResolver } from '@hookform/resolvers/zod'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import FileUploader from '../shared/FileUploader'; import Link from 'next/link'; import { myFormSchema } from './path-to-your-schema'; // 替换为你的Schema实际路径 const InfoForm = () => { // 初始化表单默认值,匹配Schema类型 const form = useForm<z.infer<typeof myFormSchema>>({ resolver: zodResolver(myFormSchema), defaultValues: { images: [], title: "", }, }); async function onSubmit(values: z.infer<typeof myFormSchema>) { console.log('表单提交值:', values); // 这里可添加文件上传到后端的逻辑 } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="title" render={({ field }) => ( <FormItem> <FormLabel>Title</FormLabel> <FormControl> <Input placeholder="输入标题" {...field} /> </FormControl> <FormDescription> Mention the key features of your item (e.g. brand, model, age, type) </FormDescription> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="images" render={({ field }) => ( <FormItem> <FormLabel>UPLOAD UP TO 20 PHOTOS</FormLabel> <FormControl> {/* 传递表单字段的value和onChange给上传组件 */} <FileUploader files={field.value} onFilesChange={field.onChange} /> </FormControl> <FormMessage /> {/* 显示Zod的验证错误提示 */} </FormItem> )} /> <div className="flex justify-between"> <Link href="/"> <Button variant="outline">Cancel</Button> </Link> <Button type="submit">Post</Button> {/* 明确指定submit类型 */} </div> </form> </Form> ); }; export default InfoForm;
额外说明
- 补充了之前缺失的
useForm导入,避免运行报错 - 新增图片删除功能,删除后表单值会自动同步更新
- Zod的
nonempty规则会在表单提交时触发验证,错误提示自动显示在FormMessage中 - 限制最大上传20张图片,和标题提示保持一致
内容的提问来源于stack exchange,提问作者Zack Snyder
相关产品推荐
相关产品推荐

