React中如何用useState维护嵌套scorecard数组的历史输入值?
React中维护嵌套数组scorecard的独立嵌套对象数据
我在useState对象里定义了空的嵌套数组scorecard,想用用户输入填充,但维护历史值时出了问题。核心需求是填充包含嵌套对象的scorecard数组,同时保留数组及内部嵌套对象的所有历史值。现在的问题是所有球洞的tees对象内容完全一致,每个球洞应该有独立的tees数据。
相关代码
AddCourse.js
const AddCourse = () => { const [courseInfo, setCourseInfo] = useState({ name: "", phone: "", website: "", address: "", city: "", state: "", zip: "", country: "", holes: "", holes: "9", teeBoxes: [], scorecard: [] }); const handleSubmit = (e) => { // Do something } return ( <div className={classes.outerCourseContainer}> <Paper elevation={4} className={classes.innerCourseContainer}> <Typography variant="h6" gutterBottom> Course Information </Typography> <Grid container spacing={2}> <Scorecard courseInfo={courseInfo} setCourseInfo={setCourseInfo} /> <Grid item xs={12} className={classes.submitCourse}> <Button variant='contained' color='primary' onClick={handleSubmit}> Submit </Button> </Grid> </Grid> </Paper> </div > ) }
Scorecard.js
let scorecardData = [...courseInfo.scorecard] let teeBox = {} const handleScorecard = (e, hole, holeIndex, tee) => { const { name, value } = e.target if (name === 'tees') { teeBox['teeBox' + tee] = { ...teeBox['teeBox' + tee], 'color': value } } if (name === 'yards') { teeBox['teeBox' + tee] = { ...teeBox['teeBox' + tee], 'yards': value } } if (name === 'par') { scorecardData[holeIndex] = { ...scorecardData[holeIndex], 'par': value, } } if (name === 'handicap') { scorecardData[holeIndex] = { ...scorecardData[holeIndex], 'handicap': value, 'Hole': hole } } scorecardData[holeIndex] = { ...scorecardData[holeIndex], tees: structuredClone(teeBox) } setCourseInfo({ ...courseInfo, scorecard: scorecardData }) } <Table className={classes.scorecardTableContainer} aria-label="scorecard"> <TableBody> <TableRow> <TableCell className={classes.scorecardHolesCell}>Holes</TableCell> {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((x, i) => ( <TableCell className={classes.scorecardHolesCell} key={i}> {x} </TableCell> ))} </TableRow> {[1, 2, 3].map((tee, teeIndex) => ( <TableRow key={teeIndex}> <TableCell> <TextField inputProps={{ maxLength: 10 }} id={`teeBoxInput${tee}`} name="tees" placeholder={`Tee ${tee}`} variant="outlined" onChange={(e) => handleScorecard(e, 0, 0, tee)} /> </TableCell> {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((hole, holeIndex) => ( <TableCell key={holeIndex}> <TextField inputProps={{ maxLength: 3 }} id={`yardInput${hole}`} name="yards" fullWidth variant="outlined" onChange={(e) => handleScorecard(e, hole, holeIndex, tee)} /> </TableCell> ))} </TableRow> ))} <TableRow> <TableCell className={classes.scorecardParCell}>Par</TableCell> {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((hole, holeIndex) => ( <TableCell key={holeIndex}> <TextField inputProps={{ maxLength: 1 }} id={`parInput${hole}`} name="par" fullWidth variant="outlined" onChange={(e) => handleScorecard(e, hole, holeIndex)} /> </TableCell> ))} </TableRow> <TableRow> <TableCell className={classes.scorecardHandicapCell}>Handicap</TableCell> {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((hole, holeIndex) => ( <TableCell key={holeIndex}> <TextField inputProps={{ maxLength: 2 }} id={`handicapInput${hole}`} name="handicap" fullWidth variant="outlined" onChange={(e) => handleScorecard(e, hole, holeIndex)} /> </TableCell> ))} </TableRow> </TableBody> </Table>
当前输出
[ { "tees": { "teeBox1": { "color": "blue", "yards": "321" }, "teeBox2": { "color": "white", "yards": "301" } }, "par": "4", "handicap": "1", "Hole": 1 }, { "tees": { "teeBox1": { "color": "blue", "yards": "321" }, "teeBox2": { "color": "white", "yards": "301" } }, "par": "4", "handicap": "2", "Hole": 2 } ]
期望输出
[ { "tees": { "teeBox1": { "color": "blue", "yards": "444" }, "teeBox2": { "color": "white", "yards": "333" } }, "par": "4", "handicap": "1", "Hole": 1 }, { "tees": { "teeBox1": { "color": "blue", "yards": "321" }, "teeBox2": { "color": "white", "yards": "301" } }, "par": "4", "handicap": "2", "Hole": 2 } ]
问题分析与修复
问题根源
- 全局
teeBox变量被所有球洞复用:每次输入都会修改同一个teeBox对象,最终所有球洞的tees都克隆这个全局对象,导致数据完全一致。 - tees颜色输入的
holeIndex固定为0:导致颜色仅更新第一个球洞的tees,但因teeBox共享,最终所有球洞同步该值。 - 状态更新未基于当前球洞的已有数据:直接覆盖
tees属性,丢失了其他球洞的独立数据。
修复后的handleScorecard函数
const handleScorecard = (e, hole, holeIndex, tee) => { const { name, value } = e.target; // 创建scorecard的副本,避免直接修改原状态 const newScorecard = [...courseInfo.scorecard]; // 确保当前球洞对象存在,不存在则初始化默认结构 const currentHole = newScorecard[holeIndex] || { tees: {}, Hole: hole }; switch(name) { case 'tees': // 更新当前球洞对应tee的颜色 newScorecard[holeIndex] = { ...currentHole, tees: { ...currentHole.tees, [`teeBox${tee}`]: { ...currentHole.tees[`teeBox${tee}`], color: value } } }; break; case 'yards': // 更新当前球洞对应tee的码数 newScorecard[holeIndex] = { ...currentHole, tees: { ...currentHole.tees, [`teeBox${tee}`]: { ...currentHole.tees[`teeBox${tee}`], yards: value } } }; break; case 'par': // 更新当前球洞的par值 newScorecard[holeIndex] = { ...currentHole, par: value }; break; case 'handicap': // 更新当前球洞的handicap和Hole编号 newScorecard[holeIndex] = { ...currentHole, handicap: value, Hole: hole }; break; default: break; } // 使用函数式更新,确保基于最新状态修改 setCourseInfo(prevState => ({ ...prevState, scorecard: newScorecard })); };
额外优化
- 给所有输入框添加
value属性,绑定对应状态值,确保受控组件行为正确:// 示例:yards输入框添加value <TextField inputProps={{ maxLength: 3 }} id={`yardInput${hole}`} name="yards" fullWidth variant="outlined" value={courseInfo.scorecard[holeIndex]?.tees?.[`teeBox${tee}`]?.yards || ''} onChange={(e) => handleScorecard(e, hole, holeIndex, tee)} /> - 如果tee颜色是整个球场共用的属性(而非每个球洞独立),建议将
teeBoxes单独维护在courseInfo中,scorecard仅存储每个球洞各tee的码数,避免数据冗余。
内容的提问来源于stack exchange,提问作者foshesco
相关产品推荐
相关产品推荐

