React嵌套JSON受控组件表单出现受控非受控报错该如何解决
问题根因
你遇到的controlled/uncontrolled报错和字段匹配问题有两个核心原因:
- 初始值里
petName/petBreed是嵌套在pet数组的第一个对象内,你把输入框的name直接设为根级字段,values根级不存在这两个字段,初始渲染时input的value为undefined,触发非受控组件逻辑,后续赋值后又转为受控组件,就会抛出对应错误 - 你直接在
value属性里写values.owner = values.owner.toUpperCase()属于直接修改state,违反React数据不可变原则,也会导致状态更新异常
第一步:修正输入框属性适配嵌套结构
要匹配你定义的初始值结构,输入框的name需要对应嵌套路径,同时禁止在value属性中直接修改state:
<Controls.Input label="Owner" name='owner' placeholder="Owner" {/* 不要在value里写=赋值,直接读取做转换即可 */} value={values.owner.toUpperCase()} onChange={handleInputChange} /> <Controls.Input label="PetName" {/* 用.区分数组索引和嵌套字段 */} name='pet.0.petName' placeholder="Pet Name" value={values.pet[0].petName.toUpperCase()} onChange={handleInputChange} /> <Controls.Input label="PetBreed" name='pet.0.petBreed' placeholder="Pet Breed" value={values.pet[0].petBreed} onChange={handleInputChange} />
第二步:改造useForm的handleInputChange支持嵌套字段更新
原来的handleInputChange只支持根级字段更新,需要修改为支持带.的嵌套路径解析:
export function useForm(initialFieldValues, validateOnChange=false, validate) { const [values, setValues] = useState(initialFieldValues); const [errors, setErrors] = useState({}); const handleInputChange = event => { const { name, value } = event.target // 解析嵌套路径 const pathSegments = name.split('.'); // 深拷贝原有values避免直接修改state const updatedValues = JSON.parse(JSON.stringify(values)); let current = updatedValues; for (let i = 0; i < pathSegments.length - 1; i++) { const key = pathSegments[i]; // 数字索引转为数值类型匹配数组 current = current[/^\d+$/.test(key) ? Number(key) : key]; } // 统一处理大写转换逻辑 current[pathSegments.at(-1)] = name.includes('Name') || name === 'owner' ? value.toUpperCase() : value; setValues(updatedValues); if(validateOnChange) { validate({ [name]: value }) } } return { values, setValues, errors, setErrors, handleInputChange, }; }
第三步:实现同个owner下添加多pet的逻辑
新增添加宠物的按钮,点击时给pet数组追加空的宠物对象,同时遍历pet数组动态生成对应输入框即可:
// 在PetsForm组件内新增添加宠物方法 const addNewPet = () => { setValues({ ...values, pet: [...values.pet, { petName: '', petBreed: '' }] }) } // 渲染部分替换原来写死的宠物输入框 <Controls.Input label="Owner" name='owner' placeholder="Owner" value={values.owner.toUpperCase()} onChange={handleInputChange} /> {values.pet.map((pet, index) => ( <Box key={index}> <Controls.Input label={`宠物${index+1}名称`} name={`pet.${index}.petName`} placeholder="Pet Name" value={pet.petName.toUpperCase()} onChange={handleInputChange} /> <Controls.Input label={`宠物${index+1}品种`} name={`pet.${index}.petBreed`} placeholder="Pet Breed" value={pet.petBreed} onChange={handleInputChange} /> </Box> ))} <Button onClick={addNewPet}>添加新宠物</Button>
内容的提问来源于stack exchange,提问作者LearnerRC
相关产品推荐
相关产品推荐

