如何使用react-hook-form修改对象数组中的值
问题解决:React Hook Form 中更新已添加数组对象的值
使用技术
JavaScript + React、react-hook-form 和 material-ui
问题说明
我有一个多分类的列表(DatasTest),用户勾选分类下的对象时,通过react-hook-form的append方法把对象添加到数组里(这部分功能正常)。现在需要实现更新已添加对象的特定值的功能。
父组件代码(含DatasTest列表)
import { Box, Stack } from '@mui/material'; import React from 'react'; import { FormReusable, Test } from '../../reusable/FormReusable'; import { useFieldArray } from 'react-hook-form'; const users = [ { engineer: 'user One', title: 'name One' }, { engineer: 'user Two', title: 'name One' }, { engineer: 'user Three', title: 'name Two' }, { engineer: 'user Four', title: 'name Three' }, ]; const DatasTest = { AssesmentDatas: [ { name: 'name One' }, { name: 'name Two' }, { name: 'name Three' }, { name: 'name Four' }, ], DesignDatas: [ { name: 'name One' }, { name: 'name Two' }, { name: 'name Three' }, { name: 'name Four' }, ], ImplementationDatas: [{ name: 'Name Five' }], }; export const ProjectType = ({ control, watch, register, replace }) => { return ( <Test DatasTest={DatasTest} control={control} watch={watch} register={register} users={users} replace={replace} /> ); };
含复选框与输入框的子组件代码
export const Test = ({ DatasTest, control, watch, users, register }) => { const currentDate = new Date(); const watchTest = watch('test'); const { fields, append, prepend, remove, swap, move, insert, replace } = useFieldArray({ control, name: 'test', }); const handleCheckboxChange = ({ event, key, index, el }) => { if (event.target.checked) { append({ labelName: key, name: el?.name, currentDate: currentDate, engineer: '', url: '', }); } else { remove(index); } }; return ( <Box> {JSON.stringify(watchTest)} {Object.keys(DatasTest).map((key) => ( <div key={key}> <h2>{key}</h2> <ul> {DatasTest[key].map((el, index) => { const filteredUsers = users?.filter((user) => user?.title.includes(el?.name) ); return ( <Box> <FormControlLabel sx={{ width: '20%', margin: '1em 2em' }} control={<Checkbox />} onChange={(event) => handleCheckboxChange({ event, key, index, el }) } label={el?.name} />{' '} <FormControl sx={{ width: '25%', margin: '1em 2em' }}> <Select {...register(`test${[index]}.engineer`)}> {filteredUsers?.map((user, index) => ( <MenuItem key={index} value={user?.engineer}> {user?.engineer} </MenuItem> ))} </Select> </FormControl>{' '} </Box> ); })} </ul> </div> ))} </Box> ); };
已尝试方法
我试过给FormControl里的engineer组件加register,用{...register(test${[index]}.engineer)}来更新,但没生效,数组里的对象根本没变化。
问题原因
你当前的逻辑有两个核心问题:
- 用DatasTest数组的索引来绑定表单字段,但这个索引和
test数组的索引完全不对应(同一个名称可能在多个分类里出现,append的顺序也和原数组无关) - 表单控件和未勾选的元素绑定,导致选择值时找不到对应的
test数组对象
解决方案
步骤1:修正复选框的增删逻辑
取消勾选时,要根据元素的唯一标识(labelName+name)找到test数组里的对应项删除,而不是用原数组索引:
const handleCheckboxChange = ({ event, key, el }) => { if (event.target.checked) { // 避免重复添加相同项 const exists = watchTest.some(item => item.labelName === key && item.name === el.name); if (!exists) { append({ labelName: key, name: el.name, currentDate: currentDate, engineer: '', url: '', }); } } else { // 找到test数组中匹配的元素索引并删除 const targetIndex = watchTest.findIndex(item => item.labelName === key && item.name === el.name); if (targetIndex !== -1) { remove(targetIndex); } } };
步骤2:拆分渲染区域,单独处理已选元素的编辑
把复选框选择区域和已选元素的编辑区域分开,确保表单控件只绑定test数组里的元素:
return ( <Box> {JSON.stringify(watchTest)} {/* 分类复选框选择区域 */} {Object.keys(DatasTest).map((key) => ( <div key={key}> <h2>{key}</h2> <ul> {DatasTest[key].map((el) => { // 判断当前元素是否已被勾选 const isChecked = watchTest.some(item => item.labelName === key && item.name === el.name); return ( <Box key={`${key}-${el.name}`}> <FormControlLabel sx={{ width: '20%', margin: '1em 2em' }} control={<Checkbox checked={isChecked} />} onChange={(event) => handleCheckboxChange({ event, key, el })} label={el.name} /> </Box> ); })} </ul> </div> ))} {/* 已选元素的编辑区域 */} <Box mt={4}> <h2>已选择项编辑</h2> {fields.map((field, index) => { const filteredUsers = users?.filter(user => user.title.includes(field.name)); return ( <Box key={field.id} sx={{ display: 'flex', alignItems: 'center', margin: '1em 0' }}> <span>{`${field.labelName} - ${field.name}`}</span> <FormControl sx={{ width: '25%', margin: '0 2em' }}> {/* 正确的字段路径:test.索引.engineer */} <Select {...register(`test.${index}.engineer`)} defaultValue={field.engineer}> {filteredUsers?.map((user, userIndex) => ( <MenuItem key={userIndex} value={user.engineer}> {user.engineer} </MenuItem> ))} </Select> </FormControl> <Button onClick={() => remove(index)} variant="outlined" color="error">删除</Button> </Box> ); })} </Box> </Box> );
步骤3:修正表单字段路径
原代码中test${[index]}.engineer是错误的,React Hook Form中数组字段的正确路径格式是数组名.索引.字段名,也就是test.${index}.engineer。
这样修改后,选择工程师时就能正确更新test数组中对应元素的engineer字段了。
内容的提问来源于stack exchange,提问作者Elodie
相关产品推荐
相关产品推荐

