You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:39:55