React Hook Form状态更新时errors对象未同步更新问题排查
问题根因
你的代码有两个核心问题导致react-hook-form(以下简称RHF)无法感知输入值变化:
- RHF的事件回调没有绑定到输入框:调用
register("jobTitle", {...})时会返回包含onChange、onBlur、name、ref的属性对象,你虽然把返回的ref通过inputRef绑到了输入框上,但最核心的onChange回调没有绑定——你自定义组件内部的输入事件只调用了自己传入的setJobTitle更新本地useState状态,完全没有触发RHF的变更回调,RHF自然收不到值更新的通知。 - 双状态源不同步:你同时用本地useState和RHF维护同一份表单值,输入时只更新了本地状态,RHF内部存储的
jobTitle字段始终是初始空值,所以校验逻辑一直判定字段为空,错误提示不会消失,也无法通过校验提交。
修复方案
推荐优先用「单状态源」的写法,完全把表单状态交给RHF管理,不用自己额外写useState存值,代码更简洁也不容易出问题。
第一步:修改自定义CustomTextBox组件
不要硬编码只接收自己定义的value/setValue属性,要把外部透传的属性(包括RHF register返回的onChange等事件)全部接住传给内部的MUI TextField:
import React from "react"; import TextField from '@material-ui/core/TextField'; // 直接继承MUI TextField的原生属性类型,不用重复定义value/onChange等通用属性 interface Props extends React.ComponentProps<typeof TextField> { // 有其他自定义属性可以在这里扩展 } const CustomTextBox = React.forwardRef<HTMLInputElement, Props>(({ id, label, variant, disabled = false, ...restProps // 接收所有外部透传的属性,包括RHF传入的onChange/name/onBlur }, ref) => { return ( <TextField id={id} label={label} variant={variant} disabled={disabled} inputRef={ref} {...restProps} // 把所有透传属性全传给内部TextField,RHF的onChange会自动绑定 /> ); }) export default CustomTextBox;
第二步:修改表单使用代码
删掉自己维护的jobTitle useState,初始值交给RHF配置,要监听值变化直接用RHF自带的watch方法即可:
import { ErrorMessage } from "@hookform/error-message"; import { Subtitle2 } from "@material/react-typography"; import React, { useEffect } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; import { CustomButton } from "../HtmlComponents/CustomButton"; import CustomTextBox from "../HtmlComponents/CustomTextBox"; const UseComponent: React.FunctionComponent = () => { type Inputs = { jobTitle: string } const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>({ defaultValues: { jobTitle: "" // 表单初始值直接在RHF中配置 } }); const onSubmit: SubmitHandler<Inputs> = data => console.log(data); // 需要监听字段值变化直接用watch,不用自己维护useState+useEffect const jobTitleWatchValue = watch("jobTitle"); useEffect(() => { console.log(jobTitleWatchValue); }, [jobTitleWatchValue]) return ( <form onSubmit={handleSubmit(onSubmit)}> <CustomTextBox id="jobTitle" variant="filled" label="Given Names *" {...register("jobTitle", { required: "Job title is required" })} /> <ErrorMessage errors={errors} name="jobTitle" render={({ message }) => <Subtitle2 style={{ color: "red" }}>{message}</Subtitle2>} /> {/* 注意提交按钮要设置type="submit"才能触发表单提交事件 */} <CustomButton id="submit" label="Save" variant="contained" type="submit" disabled={false} /> </form> ) } export default UseComponent
特殊场景:必须保留本地useState状态的写法
如果因为业务逻辑必须自己维护一份本地状态,那就在自定义组件的输入回调里同时更新本地状态和触发RHF的onChange事件,保证两边状态同步:
修改CustomTextBox的变更处理逻辑:
// 扩展Props支持传入自定义setValue interface Props extends React.ComponentProps<typeof TextField> { setValue?: React.Dispatch<React.SetStateAction<string>> } const CustomTextBox = React.forwardRef<HTMLInputElement, Props>(({ id, label, variant, disabled = false, setValue, onChange, // 接住RHF传入的onChange ...restProps }, ref) => { const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { // 先更新你自己的本地状态 setValue?.(e.target.value); // 必须调用RHF传入的onChange,把变更同步给RHF onChange?.(e); } return ( <TextField id={id} label={label} variant={variant} disabled={disabled} inputRef={ref} onChange={handleChange} {...restProps} /> ); })
这种写法下RHF默认的reValidateMode: 'onChange'配置会在值更新时自动重新校验,输入有效值后错误提示会自动消失,表单也能正常提交。
内容的提问来源于stack exchange,提问作者Lyle Phillips
相关产品推荐
相关产品推荐

