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

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
    };
  });
};

额外优化点

  1. setState是异步操作,直接在方法里console.log(this.state.serialInputObjects)拿不到最新状态,建议在setState的回调函数里打印:
this.setState(/* 更新逻辑 */, () => {
  console.log(this.state.serialInputObjects);
});
  1. 初始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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 15:30:52