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

React Hook Form结合Zod时单选按钮验证失效问题排查

React Hook Form + Zod 单选按钮验证异常解决方案

问题描述

使用React Hook Form结合Zod做表单验证时,其他输入组件验证正常,但单选按钮组存在异常:选中任一选项后,Zod仍会抛出验证错误,无法识别“选中任意选项即符合要求”的逻辑。单选按钮嵌套在同一父容器,共用错误提示容器。

代码现状

表单组件

<form onSubmit={handleSubmit(onSubmit)}>
  {/* other fields ... */}
  <div>
    <p>Receive PIN through</p>
    <label htmlFor="receive-pin-sms">
      <input
        type="radio"
        value="SMS"
        id="receive-pin-sms"
        {...register("smsOrCall")}
        name="radio"
      />
      SMS
    </label>
    <label htmlFor="receive-pin-call">
      <input
        type="radio"
        value="Call"
        id="receive-pin-call"
        {...register("smsOrCall")}
        name="radio"
      />
      Call
    </label>
    {errors.state?.message && (
      <p>{errors.state.message}</p>
    )}
  </div>
  <div>
    <button type="submit">
      Next Step
    </button>
  </div>
</form>

验证Schema

const FormSchema = z.object({
  // ...
  smsOrCall: z
    .string()
    .min(1, { message: "Please select a value" })
    .max(260, { message: "The name is too long" }),
});

RHF配置与持久化

const BusinessInformationForm: React.FC<BusinessInformationFormProps> = () => {
  const {
    register,
    formState: { errors },
    watch,
    handleSubmit,
    setValue,
  } = useForm<BusinessInformationFormInput>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      // ...
      smsOrCall: "SMS",
    },
  });

  if (typeof window !== "undefined") {
    useFormPersist("businessInformationData", {
      watch,
      setValue,
      storage: window.localStorage,
    });
  }
}

问题根源

  1. 单选按钮name属性不匹配:两个单选按钮的name设为"radio",但register绑定的字段是smsOrCall。单选按钮组必须共用与register字段名一致的name属性,否则RHF会将它们视为独立字段,导致Zod校验混乱。
  2. 错误提示字段名错误:错误提示引用了errors.state,但实际校验字段是smsOrCall,导致错误信息无法正确触发。
  3. Hook调用违反规则:useFormPersist作为React Hook,不能放在if条件判断中,违反了Hooks必须在组件顶层调用的规则。

修复方案

1. 统一单选按钮的name属性

将两个单选按钮的name改为smsOrCall,与register绑定的字段名一致:

<label htmlFor="receive-pin-sms">
  <input
    type="radio"
    value="SMS"
    id="receive-pin-sms"
    {...register("smsOrCall")}
    name="smsOrCall" // 修正为smsOrCall
  />
  SMS
</label>
<label htmlFor="receive-pin-call">
  <input
    type="radio"
    value="Call"
    id="receive-pin-call"
    {...register("smsOrCall")}
    name="smsOrCall" // 修正为smsOrCall
  />
  Call
</label>

2. 修正错误提示的字段引用

将错误提示中的errors.state改为errors.smsOrCall:

{errors.smsOrCall?.message && (
  <p>{errors.smsOrCall.message}</p>
)}

3. 规范useFormPersist的调用

将useFormPersist移到组件顶层,用类型判断处理localStorage的可用性:

const BusinessInformationForm: React.FC<BusinessInformationFormProps> = () => {
  const {
    register,
    formState: { errors },
    watch,
    handleSubmit,
    setValue,
  } = useForm<BusinessInformationFormInput>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      // ...
      smsOrCall: "SMS",
    },
  });

  // 顶层调用Hook,用条件判断处理storage参数
  useFormPersist("businessInformationData", {
    watch,
    setValue,
    storage: typeof window !== "undefined" ? window.localStorage : undefined,
  });
}

4. (可选)优化Zod Schema

用z.enum限定单选按钮的可选值,让校验更精准:

const FormSchema = z.object({
  // ...
  smsOrCall: z.enum(["SMS", "Call"], {
    required_error: "Please select a value",
  }),
});

修复效果

修改完成后,RHF会正确识别单选按钮组为一个字段,选中任一选项后Zod会校验通过;未选中任何选项时,错误提示会正常显示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:59:54