React-Hook-Form搭配Zod提交表单时无法返回全部验证错误
D&D角色表表单提交验证异常排查与解决
问题现象
- 所有字段留空时,首次提交
errors对象为空;第二次提交仅返回Level字段错误,其余必填字段无报错,不符合预期(预期所有必填字段均返回对应错误)。 - 字段失焦(Blur)时验证功能正常,比如未选择Class下拉框、HP设为负数等场景,能生成正确的错误信息。
相关代码配置
useForm 配置
const { register, handleSubmit, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(schema), mode: 'onBlur', defaultValues: schemaDefaults });
Zod 校验 Schema
const invalidNumberError = '必须输入数字' const pureNumberValidation = z.number({ invalid_type_error: invalidNumberError }); const scoreValidation = z.number({ invalid_type_error: invalidNumberError }).positive().min(1).max(30); const positiveNumberValidation = z.number({ invalid_type_error: invalidNumberError }).positive(); export const schema = z.object({ name: z.string().nonempty({ message: '姓名必填' }), class: pureNumberValidation.min(1, { message: '职业必填' }), level: positiveNumberValidation.max(20), hp: positiveNumberValidation, ac: positiveNumberValidation, speed: positiveNumberValidation, initiative: positiveNumberValidation, proficiencyBonus: positiveNumberValidation, race: pureNumberValidation.min(1, { message: '种族必填' }), alignment: pureNumberValidation.min(1, { message: '阵营必填' }), constitutionScore: scoreValidation, constitutionProficiencyBonus: pureNumberValidation, charismaScore: scoreValidation, charismaProficiencyBonus: pureNumberValidation, dexterityScore: scoreValidation, dexterityProficiencyBonus: pureNumberValidation, intelligenceScore: scoreValidation, intelligenceProficiencyBonus: pureNumberValidation, strengthScore: scoreValidation, strengthProficiencyBonus: pureNumberValidation, wisdomScore: scoreValidation, wisdomProficiencyBonus: pureNumberValidation, acrobatics: pureNumberValidation, animalhandling: pureNumberValidation, arcana: pureNumberValidation, athletics: pureNumberValidation, deception: pureNumberValidation, history: pureNumberValidation, insight: pureNumberValidation, intimidation: pureNumberValidation, investigation: pureNumberValidation, medicine: pureNumberValidation, nature: pureNumberValidation, perception: pureNumberValidation, performance: pureNumberValidation, persuasion: pureNumberValidation, religion: pureNumberValidation, sleightofhand: pureNumberValidation, stealth: pureNumberValidation, survival: pureNumberValidation });
提交错误输出
{level: {…}} level: message: "数字必须大于0" ref: input#level-input.<tailwind css classes> type: "too_small"
复用数字输入组件
<div> <label id={id} htmlFor={`${id}-input`} aria-label={label} className="<classes>" > {label} </label> <input type="number" id={`${id}-input`} {...register(name, { valueAsNumber: true })} min={min} max={max} aria-invalid={errors[name]?.message ? true : false} className='<classes>' /> {errors[name]?.message && <ErrorDisplay message={errors[name].message} />} </div>
问题原因
useForm配置mode: 'onBlur'时,React Hook Form默认仅验证**已触摸(用户点击并离开过)**的字段,提交时不会自动校验未触摸的字段。- 首次提交时所有字段均未被触摸,因此Zod未执行校验,
errors为空;第二次提交时Level字段可能被意外触摸(如点击过输入框),仅该字段被校验并返回错误。 - 数字输入框为空时,
valueAsNumber会将空值转为NaN,但未触摸的字段不会触发Zod的类型校验。
解决方案
方法1:提交时手动触发全字段验证
在提交函数中调用trigger()强制校验所有字段,确保未触摸的字段也被验证:
const { register, handleSubmit, formState: { errors }, trigger, // 引入trigger方法 } = useForm<FormData>({ resolver: zodResolver(schema), mode: 'onBlur', defaultValues: schemaDefaults }); const onSubmit = async (data: FormData) => { // 触发所有字段的验证 const isValid = await trigger(); if (!isValid) return; // 后续提交逻辑 console.log('表单数据:', data); };
方法2:修改useForm配置,强制提交时验证所有字段
调整useForm的验证模式,保留onBlur触发验证的同时,开启提交时全字段校验:
const { register, handleSubmit, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(schema), mode: 'onBlur', reValidateMode: 'onSubmit', // 提交时重新验证所有字段 defaultValues: schemaDefaults });
方法3:优化Zod Schema处理空输入场景
针对数字输入框的空值问题,使用z.coerce.number()替代z.number(),将空字符串强制转为NaN,确保即使字段未被触摸,Zod也能捕获类型错误:
// 修改数字校验规则 const pureNumberValidation = z.coerce.number({ invalid_type_error: invalidNumberError }); const scoreValidation = z.coerce.number({ invalid_type_error: invalidNumberError }).positive().min(1).max(30); const positiveNumberValidation = z.coerce.number({ invalid_type_error: invalidNumberError }).positive();
内容的提问来源于stack exchange,提问作者user1779418
相关产品推荐
相关产品推荐

