使用forwardRef自定义Input时react-hook-form的isDirty始终为false问题
问题解决:自定义forwardRef组件导致react-hook-form的isDirty/dirtyFields失效及类型错误
问题根源
react-hook-form的isDirty/dirtyFields依赖对比字段的默认值和当前值,且需要获取原生input的DOM ref来跟踪字段变化。当使用自定义forwardRef组件时,如果没有正确将ref传递到内部的原生input元素,表单无法检测到字段值的变更,从而导致isDirty始终为false、dirtyFields为空对象。你遇到的Cannot set properties of null (setting 'current')类型错误,通常是ref传递逻辑错误(比如未正确转发ref、类型定义错误)导致的。
解决方案
1. 正确实现带forwardRef的自定义Input组件
确保自定义组件将接收的ref直接传递给内部的原生input(包括styled-components包装的input),同时正确定义类型(TypeScript场景):
import React, { forwardRef } from 'react'; import styled from 'styled-components'; // 样式化原生input const StyledInput = styled.input` padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; `; // 用forwardRef转发ref到原生input元素 // 泛型参数:第一个是ref的类型(HTMLInputElement),第二个是组件props类型 const CustomInput = forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>( ({ placeholder, ...restProps }, ref) => { return <StyledInput {...restProps} ref={ref} placeholder={placeholder} />; } ); export default CustomInput;
2. 在表单中正确使用自定义组件
有两种推荐方式:
方式一:直接配合register使用
将register返回的属性(包括ref)直接传递给自定义组件:
import { useForm } from 'react-hook-form'; import CustomInput from './CustomInput'; function UserForm() { const { register, handleSubmit, formState: { isDirty, dirtyFields, touchedFields } } = useForm({ // 必须设置defaultValues,否则isDirty无法正常工作 defaultValues: { username: '', email: '' } }); const onSubmit = (formData) => { console.log('提交数据:', formData); }; return ( <form onSubmit={handleSubmit(onSubmit)} style={{ gap: '12px', display: 'flex', flexDirection: 'column' }}> <CustomInput {...register('username')} placeholder="请输入用户名" /> <CustomInput {...register('email')} placeholder="请输入邮箱" /> <button type="submit">提交表单</button> <div> <p>isDirty: {isDirty ? '✅ 是' : '❌ 否'}</p> <p>dirtyFields: </p> <pre>{JSON.stringify(dirtyFields, null, 2)}</pre> <p>touchedFields: </p> <pre>{JSON.stringify(touchedFields, null, 2)}</pre> </div> </form> ); } export default UserForm;
方式二:使用Controller组件包裹(适合复杂自定义组件)
如果自定义组件包含额外逻辑(比如状态管理、格式转换),推荐用Controller来封装,它会自动处理ref和值的同步:
import { useForm, Controller } from 'react-hook-form'; import CustomInput from './CustomInput'; function UserForm() { const { control, handleSubmit, formState: { isDirty, dirtyFields, touchedFields } } = useForm({ defaultValues: { username: '', email: '' } }); const onSubmit = (formData) => { console.log('提交数据:', formData); }; return ( <form onSubmit={handleSubmit(onSubmit)} style={{ gap: '12px', display: 'flex', flexDirection: 'column' }}> <Controller name="username" control={control} render={({ field }) => ( <CustomInput {...field} placeholder="请输入用户名" /> )} /> <Controller name="email" control={control} render={({ field }) => ( <CustomInput {...field} placeholder="请输入邮箱" /> )} /> <button type="submit">提交表单</button> <div> <p>isDirty: {isDirty ? '✅ 是' : '❌ 否'}</p> <p>dirtyFields: </p> <pre>{JSON.stringify(dirtyFields, null, 2)}</pre> <p>touchedFields: </p> <pre>{JSON.stringify(touchedFields, null, 2)}</pre> </div> </form> ); } export default UserForm;
3. 修复"Cannot set properties of null"类型错误
这个错误通常是以下原因导致:
- 自定义组件未使用
forwardRef,导致react-hook-form无法获取到原生input的ref - 类型定义错误:未正确指定ref的类型为
HTMLInputElement,导致ref被初始化为null且无法赋值 - 未设置
defaultValues:react-hook-form需要默认值来对比判断字段是否变更
按照上述步骤实现后,该错误会自动解决。
关键注意事项
- 必须设置
defaultValues:isDirty/dirtyFields是基于defaultValues和当前字段值的对比结果,如果未设置defaultValues,表单无法判断字段是否变更 - 不要手动修改react-hook-form传递的ref:让
register或Controller自动管理ref,避免手动赋值导致的null错误
内容的提问来源于stack exchange,提问作者Janah
相关产品推荐
相关产品推荐

