React Hook Form:传递register和handleSubmit作为props时报非函数错误
问题描述
我想用react-hook-form做输入验证,输入内容会随选择项变化,所以需要从父组件给子组件传递register和handleSubmit,但传完之后React抛出错误:TypeError: register和handleSubmit不是函数。我已经从最新版本切换到react-hook-form V6.12.2,问题仍未解决。
Parent.tsx
export const RequestForm =()=>{ const { handleSubmit , register} = useForm() const handleRegistration = (data:any) => console.log(data,); return ( <div className="request_form"> <form onSubmit={handleSubmit(handleRegistration)}> {(selectedServicesTypes == "Transcriptions - Audio/Video") ? (<RequestTranscriptions handleRegistration={handleRegistration} register={register} handleSubmit={handleSubmit}/>) : (selectedServicesTypes== "Closed Captions - English") ? (<RequestClosedCaptions/>) : (selectedServicesTypes == "Global Subtitles") ? (<RequestGlobalSubtitles/>) : (<NewReq />) } </form> </div> )}
可复用组件Select.tsx
export const PrimarySelect:React.FC<SelectProps> =({onChange,className, values, handleRegistration,register,handleSubmit})=>{ const getSelection = useSelector(transactionReqSelectors.selectService) return ( <FormControl sx={{ m: 1, }}> <InputLabel id="demo-multiple-name-label">Select</InputLabel> <Select value={getSelection} className={className} name="Select" // 当我改为ref={register}时不会报错,但无法获取值。 // 当我写ref={register("Select")}时会报错。 ref={register("Select")} labelId="demo-multiple-name-label" id="demo-multiple-name" onChange={onChange} input={<OutlinedInput label="Select" />}> {values?.map((name) => ( <MenuItem ref={register} value={name.type} > {name.type} </MenuItem> ))} </Select> </FormControl> )}
错误原因及修复方案
1. 核心问题:props传递断层
你在RequestForm里给RequestTranscriptions传递了register和handleSubmit,但**RequestTranscriptions没有把这两个props继续传递给它内部的PrimarySelect组件**,导致PrimarySelect里的register是undefined,调用register("Select")自然会触发"不是函数"的错误。
修复步骤:
修改RequestTranscriptions组件,确保它将父组件传入的register等props转发给PrimarySelect:
// 示例RequestTranscriptions代码 export const RequestTranscriptions = ({ register, handleRegistration }) => { return ( <div> {/* 其他表单内容 */} <PrimarySelect register={register} handleRegistration={handleRegistration} onChange={/* 你的onChange逻辑 */} className="your-class" values={/* 你的选项数据 */} /> </div> ) }
2. Material-UI Select与react-hook-form v6的适配问题
react-hook-form v6中,Material-UI的Select是受控组件,不能直接给Select标签绑定ref={register("xxx")},需要通过两种方式适配:
方案一:使用Controller组件(推荐)
Controller是react-hook-form专门用来适配第三方受控组件的工具,能自动处理值同步和验证:
// 先导入Controller import { Controller } from "react-hook-form"; export const PrimarySelect:React.FC<SelectProps> =({onChange,className, values, control})=>{ const getSelection = useSelector(transactionReqSelectors.selectService) return ( <FormControl sx={{ m: 1, }}> <InputLabel id="demo-multiple-name-label">Select</InputLabel> <Controller name="Select" // 表单数据的key,会出现在handleRegistration的data中 control={control} // 从父组件传递的useForm返回的control对象 defaultValue="" render={({ field: { onChange: rhfOnChange, value } }) => ( <Select value={value || getSelection} className={className} labelId="demo-multiple-name-label" id="demo-multiple-name" onChange={(e) => { rhfOnChange(e.target.value); // 同步到react-hook-form if (onChange) onChange(e); // 触发父组件的onChange }} input={<OutlinedInput label="Select" />}> {values?.map((name) => ( <MenuItem key={name.type} value={name.type}> {name.type} </MenuItem> ))} </Select> )} /> </FormControl> )}
注意:此时父组件需要传递control(从useForm()返回)而非register给子组件,或者使用FormProvider全局共享form上下文,避免层层传递props。
方案二:手动绑定register到内部input
如果不想用Controller,可以将register的属性拆分后绑定到Select内部的OutlinedInput上:
export const PrimarySelect:React.FC<SelectProps> =({onChange,className, values, register})=>{ const getSelection = useSelector(transactionReqSelectors.selectService) // 解构register返回的属性 const { ref, ...registerProps } = register("Select") return ( <FormControl sx={{ m: 1, }}> <InputLabel id="demo-multiple-name-label">Select</InputLabel> <Select value={getSelection} className={className} labelId="demo-multiple-name-label" id="demo-multiple-name" onChange={(e) => { registerProps.onChange(e); // 同步到react-hook-form if (onChange) onChange(e); // 触发父组件的onChange }} // 将ref和register属性绑定到内部的input上 input={<OutlinedInput label="Select" ref={ref} {...registerProps} />}> {values?.map((name) => ( <MenuItem key={name.type} value={name.type}> {name.type} </MenuItem> ))} </Select> </FormControl> )}
3. 额外优化点
- 不需要给
MenuItem加ref={register},完全无效,register只需绑定到表单控件本身。 - 父组件的
handleSubmit已经在<form>的onSubmit中使用,无需传递给子组件,子组件只需做好值的收集即可。
内容的提问来源于stack exchange,提问作者Diana

