React Hook Form中Zod的.refine不生效?如何实现条件必填校验
实现条件必填的表单校验(Zod + React Hook Form)
问题背景
我创建了一个条件输入表单,当用户选择yes时会展示另外3个输入字段。目前使用Zod的.refine方法进行校验但未正常工作,需要实现当用户选择yes时,让dryRunDate、dryRunStart、dryRunEnd这三个字段变为必填项并显示对应的警告提示。
核心问题分析
原来的.refine是全局校验逻辑,错误信息会绑定到整个表单而非单个字段,导致FormMessage无法针对每个字段显示专属的必填警告。改用Zod的**discriminatedUnion**(判别联合类型)可以完美解决这个问题,它能根据type字段的值,动态切换对应的校验规则。
解决方案
1. 重构Zod Schema
将原来的单一对象Schema拆分为两种场景的Schema,通过discriminatedUnion关联,针对type为yes的情况强制要求三个字段必填:
import { z } from "zod"; // 基础Schema,定义必填的type字段 const BaseSchema = z.object({ type: z.enum(["yes", "no"], { required_error: "请选择通知类型", }), }); // 选择yes时的Schema,三个字段必填 const YesSchema = BaseSchema.extend({ type: z.literal("yes"), dryRunDate: z.string({ required_error: "请选择试运行日期" }), dryRunStart: z.string({ required_error: "请选择试运行开始时间" }), dryRunEnd: z.string({ required_error: "请选择试运行结束时间" }), }); // 选择no时的Schema,三个字段可选 const NoSchema = BaseSchema.extend({ type: z.literal("no"), dryRunDate: z.string().optional(), dryRunStart: z.string().optional(), dryRunEnd: z.string().optional(), }); // 用判别联合类型整合两个场景 const FormSchema = z.discriminatedUnion("type", [YesSchema, NoSchema]);
2. 调整表单组件逻辑
- 将三个条件字段从
RadioGroup内部移出(RadioGroup仅应包含单选按钮,嵌套输入会引发样式和逻辑问题) - 确保
FormMessage正确绑定到每个字段,显示专属错误提示
完整组件代码:
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { z } from "zod"; // 上面的FormSchema定义... export default function RadioGroupForm() { const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), defaultValues: { type: "no", dryRunDate: "", dryRunStart: "", dryRunEnd: "" } }); const hasDryRun = form.watch("type"); const onSubmit = (data: z.infer<typeof FormSchema>) => { console.log("表单提交数据:", data); }; return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6"> {/* 单选按钮组 */} <FormField control={form.control} name="type" render={({ field }) => ( <FormItem className="space-y-3"> <FormLabel>是否需要试运行?</FormLabel> <FormControl> <RadioGroup onValueChange={field.onChange} value={field.value} className="flex flex-col space-y-1" > <FormItem className="flex items-center space-x-3 space-y-0"> <FormControl> <RadioGroupItem value="yes" /> </FormControl> <FormLabel className="font-normal">是</FormLabel> </FormItem> <FormItem className="flex items-center space-x-3 space-y-0"> <FormControl> <RadioGroupItem value="no" /> </FormControl> <FormLabel className="font-normal">否</FormLabel> </FormItem> </RadioGroup> </FormControl> <FormMessage /> </FormItem> )} /> {/* 条件显示的必填字段 */} {hasDryRun === "yes" && ( <div className="space-y-4"> <FormField control={form.control} name="dryRunDate" render={({ field }) => ( <FormItem> <FormLabel>试运行日期</FormLabel> <FormControl> <Input type="date" placeholder="选择日期" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="dryRunStart" render={({ field }) => ( <FormItem> <FormLabel>试运行开始时间</FormLabel> <FormControl> <Input type="time" placeholder="选择开始时间" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="dryRunEnd" render={({ field }) => ( <FormItem> <FormLabel>试运行结束时间</FormLabel> <FormControl> <Input type="time" placeholder="选择结束时间" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> </div> )} <Button type="submit">提交</Button> </form> </Form> ); }
效果说明
- 当选择「是」时,三个字段会自动变为必填项,未填写时对应的
FormMessage会显示自定义的错误提示 - 当选择「否」时,三个字段的校验规则自动切换为可选,不会触发必填警告
- 表单提交时会自动根据当前
type的值进行完整校验
内容的提问来源于stack exchange,提问作者Stykgwar
相关产品推荐
相关产品推荐

