React Hook Form中Controller返回[Function Anonymous]而非数组数据问题
解决SkillsInput组件返回函数而非数组导致的Yup校验错误
问题根源
核心问题是SkillsInput组件内部嵌套了独立的useForm实例,和父组件Detail的form状态完全隔离:
- 子组件内部的
items是自身form实例的状态,虽然打印正常,但父组件根本拿不到这个值 - 父组件通过自身Controller获取子组件的值时,实际拿到的是子组件内部某个函数引用(即你看到的
[Function Anonymous]),最终被转换为null触发Yup的数组类型校验错误
修复方案
不要在子组件里单独用useForm,而是复用父组件的form控制实例(control)和字段名(name),让子组件的状态直接挂载到父组件的form状态中。
1. 改造SkillsInput组件
移除内部的useForm,改为接收父组件传递的control和name,用useWatch监听字段值,直接通过父组件的control.setValue操作数组:
import { Controller, useWatch } from "react-hook-form"; const SkillsInput = ({ control, name }) => { // 监听父组件form中对应字段的值 const items = useWatch({ control, name }); // 添加技能:更新父组件form的skills数组 const addSkill = () => { control.setValue(name, [...items, ""]); }; // 删除指定索引的技能 const removeSkill = (index) => { const updatedItems = items.filter((_, i) => i !== index); control.setValue(name, updatedItems); }; // 更新指定索引的技能值 const updateSkill = (index, value) => { const updatedItems = [...items]; updatedItems[index] = value; control.setValue(name, updatedItems); }; return ( <Controller control={control} name={name} render={({ field }) => ( <div> {field.value.map((skill, index) => ( <div key={index} style={{ margin: "8px 0" }}> <input value={skill} onChange={(e) => updateSkill(index, e.target.value)} placeholder="输入技能" /> <button onClick={() => removeSkill(index)} style={{ marginLeft: "8px" }}> 删除 </button> </div> ))} <button onClick={addSkill}>添加技能</button> </div> )} /> ); }; export default SkillsInput;
2. 在Detail组件中使用改造后的SkillsInput
传递父组件的control和字段名skills,同时保持Yup校验规则不变:
import { useForm, Controller } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Yup from "yup"; import SkillsInput from "./SkillsInput"; // Yup校验规则 const formSchema = Yup.object().shape({ skills: Yup.array() .of(Yup.string().required("技能不能为空")) .required("至少添加一项技能"), // 其他字段规则... }); const Detail = () => { const { control, handleSubmit, formState: { errors } } = useForm({ defaultValues: { skills: [], // 其他字段默认值... }, resolver: yupResolver(formSchema), }); const onSubmit = (data) => { console.log("提交数据:", data); // 这里的data.skills就是正确的数组类型 }; return ( <form onSubmit={handleSubmit(onSubmit)} style={{ padding: "20px" }}> <h3>技能输入</h3> <SkillsInput control={control} name="skills" /> {errors.skills && <p style={{ color: "red" }}>{errors.skills.message}</p>} {/* PreferencesInput组件同理,传递control和对应name */} {/* <PreferencesInput control={control} name="preferences" /> */} <button type="submit" style={{ marginTop: "20px" }}>提交</button> </form> ); }; export default Detail;
3. 校验规则说明
如果需要允许skills为空数组,可以把Yup规则改成:
skills: Yup.array().of(Yup.string().required()).nullable()
但根据你的需求,保持原规则即可,只要父组件能正确拿到数组值,校验就会正常通过。
补充说明
react-hook-form的每个useForm实例都是独立的状态容器,嵌套使用会导致状态割裂,父组件无法感知子组件的内部状态变化。复用父组件的control是实现嵌套表单组件的正确方式,PreferencesInput组件也需要按照同样的逻辑改造。
内容的提问来源于stack exchange,提问作者etcN00b
相关产品推荐
相关产品推荐

