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

React Hook Form:如何避免重复向多个TextField传递Control和Errors props

解决react-hook-form重复传递control/errors的方案

核心思路:用React Context共享全局表单上下文

你之前用自定义hook返回组件导致失焦,本质是每次调用hook都会生成全新的组件构造函数,组件每次渲染都会被重新挂载,自然会丢失输入焦点。用Context是更稳妥的方案——把control和errors放到上下文里,让封装的TextfieldController自动读取,不用手动重复传参。

步骤1:创建表单上下文

先定义一个Context来存储react-hook-form的核心实例数据:

// FormContext.js
import { createContext, useContext } from 'react';

const FormContext = createContext(null);

export const FormProvider = FormContext.Provider;
export const useFormContext = () => {
  const context = useContext(FormContext);
  if (!context) {
    throw new Error('useFormContext必须在FormProvider内部使用');
  }
  return context;
};

步骤2:在表单根组件中注入上下文

在调用useForm的组件里,把control和errors通过Provider传递给所有子组件:

// 你的表单组件
import { useForm } from 'react-hook-form';
import { FormProvider } from './FormContext';
import TextfieldController from './TextfieldController';

function MyForm() {
  const { control, formState: { errors } } = useForm();

  return (
    <FormProvider value={{ control, errors }}>
      <TextfieldController name="username" label="用户名" rules={{ required: '用户名必填' }} />
      <TextfieldController name="email" label="邮箱" rules={{ required: '邮箱必填', pattern: { value: /^\S+@\S+$/i, message: '邮箱格式不正确' } }} />
      {/* 其他表单组件直接用,不用传control/errors */}
    </FormProvider>
  );
}

步骤3:修改TextfieldController消费上下文

改造你封装的组件,不再接收control和errors,而是通过useFormContext自动获取:

// TextfieldController.js
import { Controller } from 'react-hook-form';
import TextField from '@mui/material/TextField'; // 这里假设你用MUI的TextField,可根据实际组件替换
import { useFormContext } from './FormContext';

function TextfieldController({ name, label, rules, ...rest }) {
  const { control, errors } = useFormContext();

  return (
    <Controller
      name={name}
      control={control}
      rules={rules}
      render={({ field }) => (
        <TextField
          {...field}
          label={label}
          error={!!errors[name]}
          helperText={errors[name]?.message}
          {...rest}
        />
      )}
    />
  );
}

export default TextfieldController;

为什么这个方案不会失焦?

因为TextfieldController是一个固定的组件,不会每次渲染都重新创建,只是在内部读取上下文的值,组件的身份始终稳定,所以onChange时不会触发卸载/重新挂载,自然不会丢失输入焦点。

额外优化:封装复用型Form组件

如果多个页面都需要这个逻辑,可以把FormProvider和useForm的逻辑封装成通用组件:

// ReusableForm.js
import { useForm } from 'react-hook-form';
import { FormProvider } from './FormContext';

function ReusableForm({ children, defaultValues, ...formOptions }) {
  const { control, formState: { errors }, handleSubmit, reset } = useForm({
    defaultValues,
    ...formOptions
  });

  return (
    <FormProvider value={{ control, errors }}>
      {children({ handleSubmit, reset })}
    </FormProvider>
  );
}

export default ReusableForm;

使用时会更简洁:

function MyForm() {
  return (
    <ReusableForm defaultValues={{ username: '', email: '' }}>
      {({ handleSubmit }) => (
        <form onSubmit={handleSubmit((data) => console.log(data))}>
          <TextfieldController name="username" label="用户名" rules={{ required: '用户名必填' }} />
          <TextfieldController name="email" label="邮箱" rules={{ required: '邮箱必填' }} />
          <button type="submit">提交</button>
        </form>
      )}
    </ReusableForm>
  );
}

内容的提问来源于stack exchange,提问作者Thushan D.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 15:02:23