Zod+React Hook Form多字段联动验证的错误显示与清除问题
Zod + React Hook Form 跨字段校验:双字段错误同步显示与清除
问题分析
你遇到的两个核心问题:
refine多路径失效:Zod的refine配置中path仅支持单个字段路径,传入数组会导致解析异常,因此错误仅在第一个字段显示且信息变为unknown。superRefine错误未同步清除:React Hook Form默认仅校验用户修改过的「脏字段」,当条件满足时,未被编辑的字段不会自动重新校验,错误残留。
解决方案
1. 保留superRefine实现双字段错误标记
superRefine是给多个字段添加自定义错误的正确方式,继续使用这个逻辑确保错误信息显示在fieldTwo和fieldThree上。
2. 强制同步校验两个字段
通过监听三个字段的变化,任一字段更新时触发fieldTwo和fieldThree的校验,确保条件满足时两个字段的错误同时清除。以下是两种可行实现方式:
方式一:字段onChange时触发校验
在fieldTwo和fieldThree的onChange回调中手动触发对方字段的校验,同时监听fieldOne的变化触发两个字段的校验。
方式二:watch+useEffect批量触发校验
通过watch监听三个字段的值变化,任一值改变时批量触发fieldTwo和fieldThree的校验,代码更简洁。
完整修改代码
以下是watch+useEffect的实现示例:
import { Button } from "@/components/ui/button"; import { DialogFooter } from "@/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useEffect } from "react"; const Test = () => { const schema = z .object({ fieldOne: z.coerce.number(), fieldTwo: z.coerce.number(), fieldThree: z.coerce.number(), }) .superRefine((args, ctx) => { const sum = args.fieldTwo + args.fieldThree; if (sum !== args.fieldOne) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["fieldTwo"], message: "fieldTwo + fieldThree must equal fieldOne", }); ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["fieldThree"], message: "fieldTwo + fieldThree must equal fieldOne", }); } }); const form = useForm<z.infer<typeof schema>>({ resolver: zodResolver(schema), }); // 监听三个字段变化,同步触发fieldTwo和fieldThree的校验 const fieldOne = form.watch("fieldOne"); const fieldTwo = form.watch("fieldTwo"); const fieldThree = form.watch("fieldThree"); useEffect(() => { form.trigger(["fieldTwo", "fieldThree"]); }, [fieldOne, fieldTwo, fieldThree, form]); const onSubmit = (values: z.infer<typeof schema>) => { console.log("SUBMIT::", values); }; return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="fieldOne" render={({ field }) => ( <FormItem className="grid grid-cols-4 items-center !mt-2 gap-4"> <FormLabel className="text-right">FieldOne</FormLabel> <div className="col-span-3"> <FormControl> <Input placeholder="Enter fieldOne" {...field} /> </FormControl> <FormMessage className="mt-2" /> </div> </FormItem> )} /> <FormField control={form.control} name="fieldTwo" render={({ field }) => ( <FormItem className="grid grid-cols-4 items-center !mt-2 gap-4"> <FormLabel className="text-right">FieldTwo</FormLabel> <div className="col-span-3"> <FormControl> <Input placeholder="Enter fieldTwo" {...field} /> </FormControl> <FormMessage className="mt-2" /> </div> </FormItem> )} /> <FormField control={form.control} name="fieldThree" render={({ field }) => ( <FormItem className="grid grid-cols-4 items-center !mt-2 gap-4"> <FormLabel className="text-right">FieldThree</FormLabel> <div className="col-span-3"> <FormControl> <Input placeholder="Enter fieldThree" {...field} /> </FormControl> <FormMessage className="mt-2" /> </div> </FormItem> )} /> <DialogFooter> <Button type="submit" className="w-1/4"> Submit </Button> </DialogFooter> </form> </Form> ); }; export default Test;
关键说明
superRefine逻辑不变:当和不等于fieldOne时,给两个字段添加自定义错误。useEffect监听字段变化:只要fieldOne、fieldTwo、fieldThree任一值改变,就触发两个字段的校验,确保错误状态同步更新。- 替代方案:若不想用
useEffect,可在每个字段的onChange中添加触发逻辑,例如fieldTwo的onChange改为:
同时给onChange={(e) => { field.onChange(e); form.trigger("fieldThree"); }}fieldOne和fieldThree做类似处理。
内容的提问来源于stack exchange,提问作者rejam_
相关产品推荐
相关产品推荐

