You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 00:53:09