React状态中移除嵌套对象且不影响渲染循环的解决方案
动态添加/移除文本字段时的渲染错误修复
尝试为用户输入动态添加和移除文本字段,点击移除按钮删除某一行时,希望修改state移除该行数据让对应行消失,但操作后出现报错:
Cannot read properties of undefined (reading 'from')期望组件能根据新的state正确显示对应行数,相关代码如下:
import "./styles.css"; import React from "react"; import { Button, Grid, Paper } from "@mui/material"; import { TextField, Icon } from "@mui/material"; interface State { serialInputObjects: any; } class SerialQRScanClass extends React.PureComponent<State> { state = { serialInputObjects: { 0: { from: "", to: "", except: "" } } }; //Delete the already registered scanned codes code here handleAdd = () => { const objectLength = Object.keys(this.state.serialInputObjects).length; console.log(objectLength); this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [objectLength]: { from: "", to: "", except: "", fromError: "", toError: "" } } })); console.log(this.state.serialInputObjects); }; handleChangeFromSerials = (key: any, data: string) => { this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [key]: { ...prevState.serialInputObjects[key], from: data } } })); console.log(this.state.serialInputObjects); //this.calculation(key); }; handleChangeToSerials = (key: any, data: string) => { this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [key]: { ...prevState.serialInputObjects[key], to: data } } })); console.log(this.state.serialInputObjects); //this.calculation(key); }; handleRemove = (key) => { console.log(this.state.serialInputObjects); this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [key]: undefined } })); console.log(this.state.serialInputObjects); }; render() { return ( <Paper elevation={3} className="abc"> <Button onClick={this.handleAdd}>ADD NEW FIELD</Button> {Object.keys(this.state.serialInputObjects).map((key) => ( <div key={key}> <Grid container alignItems="flex-end"> <Grid item className="bcd"> <TextField fullWidth label={"FROM"} placeholder={"Ex.100"} value={this.state.serialInputObjects[key]["from"]} onChange={(e) => this.handleChangeFromSerials(key, e.target.value) } error={ Boolean(this.state.serialInputObjects[key]["fromError"]) || false } helperText={this.state.serialInputObjects[key]["fromError"]} margin="none" size="small" /> </Grid> <Grid item className="bcd"> <TextField fullWidth label={"To"} placeholder={"Ex.100"} value={this.state.serialInputObjects[key]["to"]} onChange={(e) => this.handleChangeToSerials(key, e.target.value) } error={ Boolean(this.state.serialInputObjects[key]["toError"]) || false } helperText={this.state.serialInputObjects[key]["toError"]} margin="none" size="small" /> </Grid> <Grid item className={"abc"} style={{ paddingLeft: "10px" }} ></Grid> <div style={{ display: key === "0" ? "none" : "block" }}> <Button onClick={(e) => this.handleRemove(key)}> <Icon fontSize="small">remove_circle</Icon> </Button> </div> </Grid> </div> ))} </Paper> ); } } export default function App() { return ( <div className="App"> <SerialQRScanClass /> </div> ); }
错误原因
问题出在handleRemove方法:你只是把对应key的对象设为undefined,但这个key仍然存在于serialInputObjects对象中。Object.keys()会遍历所有存在的key(包括值为undefined的),渲染时访问this.state.serialInputObjects[key]["from"]就会因为值是undefined而报错。
修复方案
彻底删除目标key,而不是将其值设为undefined。可以通过解构赋值排除要删除的key,生成新对象:
handleRemove = (key) => { this.setState((prevState) => { const { [key]: _, ...newSerialInputObjects } = prevState.serialInputObjects; return { ...prevState, serialInputObjects: newSerialInputObjects }; }); };
额外优化点
setState是异步操作,直接在方法里console.log(this.state.serialInputObjects)拿不到最新状态,建议在setState的回调函数里打印:
this.setState(/* 更新逻辑 */, () => { console.log(this.state.serialInputObjects); });
- 初始state中的对象缺少
fromError和toError属性,与后续添加的对象结构不一致,建议补充保持结构统一,避免潜在报错:
state = { serialInputObjects: { 0: { from: "", to: "", except: "", fromError: "", toError: "" } } };
完整修复后代码
import "./styles.css"; import React from "react"; import { Button, Grid, Paper } from "@mui/material"; import { TextField, Icon } from "@mui/material"; interface State { serialInputObjects: any; } class SerialQRScanClass extends React.PureComponent<State> { state = { serialInputObjects: { 0: { from: "", to: "", except: "", fromError: "", toError: "" } } }; handleAdd = () => { const objectLength = Object.keys(this.state.serialInputObjects).length; this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [objectLength]: { from: "", to: "", except: "", fromError: "", toError: "" } } }), () => { console.log(this.state.serialInputObjects); }); }; handleChangeFromSerials = (key: any, data: string) => { this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [key]: { ...prevState.serialInputObjects[key], from: data } } }), () => { console.log(this.state.serialInputObjects); }); }; handleChangeToSerials = (key: any, data: string) => { this.setState((prevState) => ({ ...prevState, serialInputObjects: { ...prevState.serialInputObjects, [key]: { ...prevState.serialInputObjects[key], to: data } } }), () => { console.log(this.state.serialInputObjects); }); }; handleRemove = (key) => { this.setState((prevState) => { const { [key]: removedItem, ...newSerialInputObjects } = prevState.serialInputObjects; return { ...prevState, serialInputObjects: newSerialInputObjects }; }, () => { console.log(this.state.serialInputObjects); }); }; render() { return ( <Paper elevation={3} className="abc"> <Button onClick={this.handleAdd}>ADD NEW FIELD</Button> {Object.keys(this.state.serialInputObjects).map((key) => ( <div key={key}> <Grid container alignItems="flex-end"> <Grid item className="bcd"> <TextField fullWidth label={"FROM"} placeholder={"Ex.100"} value={this.state.serialInputObjects[key]["from"]} onChange={(e) => this.handleChangeFromSerials(key, e.target.value) } error={ Boolean(this.state.serialInputObjects[key]["fromError"]) || false } helperText={this.state.serialInputObjects[key]["fromError"]} margin="none" size="small" /> </Grid> <Grid item className="bcd"> <TextField fullWidth label={"To"} placeholder={"Ex.100"} value={this.state.serialInputObjects[key]["to"]} onChange={(e) => this.handleChangeToSerials(key, e.target.value) } error={ Boolean(this.state.serialInputObjects[key]["toError"]) || false } helperText={this.state.serialInputObjects[key]["toError"]} margin="none" size="small" /> </Grid> <Grid item className={"abc"} style={{ paddingLeft: "10px" }} ></Grid> <div style={{ display: key === "0" ? "none" : "block" }}> <Button onClick={(e) => this.handleRemove(key)}> <Icon fontSize="small">remove_circle</Icon> </Button> </div> </Grid> </div> ))} </Paper> ); } } export default function App() { return ( <div className="App"> <SerialQRScanClass /> </div> ); }
内容的提问来源于stack exchange,提问作者akshay_acharya
相关产品推荐
相关产品推荐

