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

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名称笔误:

  1. ModuleAddressSchema已经为每个嵌套字段(street/houseNumber等)定义了min(1)的必填验证,无需再给整个address对象加全局refine。
  2. 全局refine的错误会被挂载到errors.address上,而非单个字段(如errors.address.street),导致判断单个输入框错误时无法获取对应值。
  3. 初始化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:57:20