React Hook Form如何确保onChange触发前先完成字段校验
React Hook Form 自定义onChange执行早于字段校验问题解决方案
问题根因
register配置项中传入的自定义onChange回调,执行时机早于React Hook Form内部的表单值同步、校验执行、errors状态更新流程。回调中读取到的errors是上一轮组件渲染留下的旧闭包值,无法反映当前输入的校验结果,所以会出现无效输入也进入后续业务逻辑的问题。
可行实现方案
不要在register的自定义onChange回调中直接依赖errors做判断,以下两种方案时序稳定,符合React Hook Form的设计逻辑:
方案1:监听字段值+主动触发校验
用watch监听目标字段值变化,值变动后主动调用trigger触发对应字段校验,等待校验返回结果后,再判断是否执行自动填充逻辑,示例代码:const { handleSubmit, formState: { errors }, register, watch, trigger, setValue } = useForm<FormValues>({ mode: 'onChange' }); // 监听邮政编码字段值变化 const postalCodeValue = watch('postalCode'); useEffect(() => { const runAddressFill = async () => { // 主动触发校验,等待校验结果返回 const isValid = await trigger('postalCode'); if (!isValid) return; // 校验通过后执行地址查询,拿到结果用setValue填充关联字段 // const addressInfo = await fetchAddressByPostalCode(postalCodeValue); // setValue('province', addressInfo.province); // setValue('city', addressInfo.city); // setValue('detail', addressInfo.detail); }; runAddressFill(); }, [postalCodeValue, trigger, setValue]); <input type="text" {...register('postalCode', { // 在这里配置postalCode的所有校验规则 required: '请输入邮政编码', pattern: { value: /^[0-9]{6}$/, message: '请输入6位有效邮政编码' } })} />注意:填充关联表单字段必须使用
setValue方法,不要直接操作DOM或用本地state赋值,否则会脱离React Hook Form的状态管理,导致后续提交、校验取值异常。方案2:依赖校验状态更新触发逻辑
利用errors状态更新会触发组件重渲染的机制,用ref记录已经处理过的字段值,每次渲染判断当前字段无校验错误、值未被处理过时,执行自动填充逻辑:const { handleSubmit, formState: { errors }, register, getValues, setValue } = useForm<FormValues>({ mode: 'onChange' }); // 记录已处理过的邮政编码,避免重复发起请求 const handledCode = useRef(''); useEffect(() => { const currentCode = getValues('postalCode'); if (!errors.postalCode && currentCode && currentCode !== handledCode.current) { handledCode.current = currentCode; // 执行地址查询、关联字段填充逻辑 } }, [errors.postalCode, getValues, setValue]); <input type="text" {...register('postalCode', { required: '请输入邮政编码', pattern: { value: /^[0-9]{6}$/, message: '请输入6位有效邮政编码' } })} />
避坑说明
- 不要在
register的自定义onChange里用setTimeout等方式等待errors更新,时序不可靠,高概率出现竞态问题 - 不要直接解构
formState后在回调里读取状态,React状态批处理机制下回调中拿到的永远是旧值 - 如果自动填充的字段本身也有校验规则,调用
setValue时可以加shouldValidate: true配置,触发填充字段的自动校验
内容的提问来源于stack exchange,提问作者JMU
相关产品推荐
相关产品推荐

