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

如何用Zod对含嵌套字段的React Hook Form做条件验证?

解决React Hook Form + Zod的条件联系方式验证问题

现有方案的核心问题

  1. 全局.refine()会在其他字段验证未通过时强制执行,导致用户刚解决完当前错误,又弹出新的联系方式验证错误,体验糟糕;
  2. 取消勾选联系方式后,对应字段的验证规则仍生效,错误信息无法自动清除。

重构后的Zod Schema(用z.when实现条件验证)

利用Zod的z.when方法,将验证逻辑与字段绑定,仅在对应联系方式被勾选时触发验证,未勾选时字段自动变为可选,从根源避免错误残留:

import { z } from "zod";
import validator from "validator";
import parsePhoneNumber from "libphonenumber-js";

const schema = z.object({
  // 保留你的其他字段
  contactMethods: z
    .array(z.enum(["email", "phone"]))
    .min(1, "At least one contact method is required"),
  email: z.when(
    // 判断条件:是否勾选了email
    (data) => data.contactMethods.includes("email"),
    {
      // 勾选时:必填+格式验证
      required_error: "Enter an email",
      schema: z.string().trim().email("Enter a valid email"),
    },
    {
      // 未勾选时:允许空值/undefined,不触发验证
      schema: z.string().optional().nullable(),
    }
  ),
  phone: z.when(
    (data) => data.contactMethods.includes("phone"),
    {
      // 勾选时:必填+格式处理+转换
      required_error: "Enter a phone number",
      schema: z.string()
        .trim()
        .transform(val => val.replaceAll(" ", ""))
        .refine(val => validator.isMobilePhone(val), "Enter a valid phone number")
        .transform(val => parsePhoneNumber(val, "GB")?.number),
    },
    {
      // 未勾选时:允许空值/undefined
      schema: z.string().optional().nullable(),
    }
  ),
});

配合React Hook Form处理字段重置

为了彻底清除未勾选字段的错误,需要在用户取消勾选时清空对应字段的值,并触发重新验证:

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

function ContactForm() {
  const { register, setValue, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
    defaultValues: {
      contactMethods: [],
      email: "",
      phone: "",
      // 其他字段默认值
    },
  });

  // 在勾选框的change事件中直接处理字段重置
  const handleContactMethodToggle = (method, isChecked) => {
    if (!isChecked) {
      setValue(method, "", { shouldValidate: true });
    }
  };

  return (
    <form>
      {/* 联系方式勾选框 */}
      <label>
        <input
          type="checkbox"
          {...register("contactMethods", { value: "email" })}
          onChange={(e) => handleContactMethodToggle("email", e.target.checked)}
        />
        Email
      </label>
      <label>
        <input
          type="checkbox"
          {...register("contactMethods", { value: "phone" })}
          onChange={(e) => handleContactMethodToggle("phone", e.target.checked)}
        />
        Phone
      </label>

      {/* 联系方式输入框 */}
      {errors.contactMethods && <p className="error">{errors.contactMethods.message}</p>}
      
      <input
        type="email"
        {...register("email")}
        placeholder="Enter your email"
      />
      {errors.email && <p className="error">{errors.email.message}</p>}

      <input
        type="tel"
        {...register("phone")}
        placeholder="Enter your phone number"
      />
      {errors.phone && <p className="error">{errors.phone.message}</p>}

      {/* 其他字段和提交按钮 */}
    </form>
  );
}

方案优势

  1. 验证顺序合理:字段级别的条件验证会和其他字段同步验证,不会出现“解决完一个错误又冒新错误”的情况;
  2. 错误自动清除:取消勾选时清空字段并触发验证,此时Zod会识别该字段为可选,自动清除错误信息;
  3. 代码更简洁:去掉冗余的全局.refine(),逻辑更直观,维护成本更低。

内容的提问来源于stack exchange,提问作者parasomnist

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 21:33:17