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, }); } }
问题根源
- 单选按钮name属性不匹配:两个单选按钮的
name设为"radio",但register绑定的字段是smsOrCall。单选按钮组必须共用与register字段名一致的name属性,否则RHF会将它们视为独立字段,导致Zod校验混乱。 - 错误提示字段名错误:错误提示引用了
errors.state,但实际校验字段是smsOrCall,导致错误信息无法正确触发。 - 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
相关产品推荐
相关产品推荐

