React Hook Form + Zod: formState返回isValid正常但errors始终为空
React Hook Form + Zod 验证时 errors 对象为空的解决方法
问题场景
使用React Hook Form的useForm钩子搭配Zod做表单验证时,formState返回的touchedFields、isValid等值都符合预期,但errors对象始终为空。调试时能看到Zod的.refine()方法返回了正确的错误信息,但无法映射到RHF的errors中,导致输入框的hasError状态无法正确触发。
问题原因分析
核心问题出在Zod Schema中给address对象添加的多余.refine()方法,以及可能存在的Schema名称笔误:
ModuleAddressSchema已经为每个嵌套字段(street/houseNumber等)定义了min(1)的必填验证,无需再给整个address对象加全局refine。- 全局refine的错误会被挂载到
errors.address上,而非单个字段(如errors.address.street),导致判断单个输入框错误时无法获取对应值。 - 初始化
useForm时传入的OpeningHoursBodySchema和定义的OpeningHoursSchema名称不匹配,可能导致Resolver未正确绑定验证规则。
解决方案
1. 修正Zod Schema,移除多余的全局refine
保留ModuleAddressSchema中每个字段的独立验证,去掉address对象上的refine:
export const ModuleAddressSchema = z.object({ street: z.string().min(1, { message: REQUIRED }), houseNumber: z.string().min(1, { message: REQUIRED }), city: z.string().min(1, { message: REQUIRED }), zip: z.string().min(1, { message: REQUIRED }), }); export const OpeningHoursSchema = z.object({ eventId: z.number(), startDate: z.string().min(1, { message: "Date should not be empty" }), endDate: z.string().min(1, { message: "Date should not be empty" }), timezone: z.string().optional(), weeklyOpeningTimes: WeeklyOpeningTimesSchema, address: ModuleAddressSchema, // 移除全局refine,保留字段级验证 });
2. 确保useForm初始化时绑定正确的Schema
检查resolver传入的Schema名称是否和定义一致:
const { register, watch, reset, getValues, setValue, formState: { errors, touchedFields, isValid, isDirty }, } = useForm({ defaultValues: initialValues, resolver: zodResolver(OpeningHoursSchema), // 匹配定义的Schema名称 });
3. 验证initialValues的结构完整性
确保initialValues中的address是完整的对象结构,而非undefined或null:
const initialValues = { // 其他字段... address: { street: '', houseNumber: '', city: '', zip: '', }, };
4. (可选)如果需要对象级验证,指定错误路径
如果确实需要对整个address对象做字段关联验证(如邮编和城市匹配),需在refine中通过path指定错误挂载的位置,确保RHF能正确映射到对应字段:
address: ModuleAddressSchema.refine( (moduleAddress) => { // 示例:验证邮编与城市匹配 return moduleAddress.zip.startsWith('10') && moduleAddress.city === 'Beijing'; }, { message: "Zip code doesn't match city", path: ['zip'] // 将错误挂载到zip字段 } ),
验证效果
修正后,当street字段为空时,errors.address.street会被正确赋值,输入框的hasError={touchedFields.address?.street && !!errors.address?.street}逻辑就能正常触发错误状态。
内容的提问来源于stack exchange,提问作者Asen Georgiev
相关产品推荐
相关产品推荐

