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

React Hook Form状态更新时errors对象未同步更新问题排查

问题根因

你的代码有两个核心问题导致react-hook-form(以下简称RHF)无法感知输入值变化:

  1. RHF的事件回调没有绑定到输入框:调用register("jobTitle", {...})时会返回包含onChange、onBlur、name、ref的属性对象,你虽然把返回的ref通过inputRef绑到了输入框上,但最核心的onChange回调没有绑定——你自定义组件内部的输入事件只调用了自己传入的setJobTitle更新本地useState状态,完全没有触发RHF的变更回调,RHF自然收不到值更新的通知。
  2. 双状态源不同步:你同时用本地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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 04:36:15