首页 > 解决方案 > componentDidUpdate 中的 setState 将 prev 与当前状态进行比较,无法用新数据重新渲染页面,也会出现无限循环

问题描述

我是 React 的初学者,在这种特殊情况下,我可能遗漏了一些非常基本的东西。

在这里,我有一个简单的 CRUD 应用程序,在用户添加新数据后,应该呈现更新的项目列表。新数据通过第二个 Dialog 组件AddNewDevelopmentWork.js 添加

因此,在AddNewDevelopmentWork.js(它是只会打开一个对话框供用户输入和填写几个 TestFields 的子组件)添加新数据后,在主组件(DevelopmentList.js)中,我使用componentDidUpdate来与当前状态和 prevState 进行比较(对于状态变量allDevelopmentWorks,它是一个对象数组),如果它们不相等,则向后端 Express API 发出请求,并在componentDidUpdate中获取数据和更新状态。然后用新数据渲染。

问题是,这个主要的DevelopmentList.js组件在页面刷新之前不会呈现用户输入的新数据。但是在手动刷新页面后,它会显示新输入的数据。

这是我的DevelopmentList组件。

 class DevelopmentList extends Component {
      constructor(props) {
        super(props);
        this.state = {
          allDevelopmentWorks: []
        };
      }

    componentDidUpdate(prevProps, prevState) {
        if (
          this.state.allDevelopmentWorks.length !==
          prevState.allDevelopmentWorks.length
        ) {
          return axios
            .get("/api/developmenties")
            .then(res => {
              this.setState({
                allDevelopmentWorks: res.data
              });                  
            })
            .catch(function(error) {
              console.log(error);
            });
        }
      }

      componentDidMount() {
        axios.get("/api/developmenties").then(res => {
          this.setState({
            allDevelopmentWorks: res.data
          });
        });
      }

    render() {
const { classes } = this.props;
return (
  <div>
    <Table className={classes.table}>
      <TableHead>
        <TableRow className={classes.row}>
          <CustomTableCell align="left">Location</CustomTableCell>
          <CustomTableCell align="left">
            Description Of Work
          </CustomTableCell>
          <CustomTableCell align="left">
            Date of Commencement
          </CustomTableCell>
          <CustomTableCell align="left">Date of Completion</CustomTableCell>
          <CustomTableCell align="left">Status of Work</CustomTableCell>
        </TableRow>
      </TableHead>
      <TableBody>
        {this.state.allDevelopmentWorks.map((document, i) => (
          <TableRow className={classes.row} key={i}>
            <CustomTableCell component="th" scope="row">
              {document.location}
            </CustomTableCell>
            <CustomTableCell align="left">
              {document.work_description}
            </CustomTableCell>
            <CustomTableCell align="left">
              {moment(document.date_of_commencement).format("YYYY-MM-DD")}
            </CustomTableCell>
            <CustomTableCell align="left">
              {moment(document.date_of_completion).format("YYYY-MM-DD")}
            </CustomTableCell>
            <CustomTableCell align="left">
              {document.status_of_work}
            </CustomTableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  </div>
);

  }
}
export default withStyles(styles)(DevelopmentList);

但是,使用下面的componentDidUpdate方法,如果我将其更改为如下 if 条件(将长度属性排除在等式之外),则新数据会立即呈现在页面上,但随后它也会在componentDidUpdate内部变成无限循环并命中每秒一次又一次地使用 Express API。

componentDidUpdate(prevProps, prevState) {
    if (
      this.state.allDevelopmentWorks !==
      prevState.allDevelopmentWorks
    ) {
      return axios
        .get("/api/developmenties")
        .then(res => {
          this.setState({
            allDevelopmentWorks: res.data
          });

    })
    .catch(function(error) {
      console.log(error);
    });
}

 }

第二个组件中的代码(它是主 DevelopmentList.js组件的子组件,只会打开一个对话框供用户输入并填充几个 TestFields 将新数据添加到此 CRUD)在AddNewDevelopmentWork.js下面

class AddNewDevelopmentWork extends Component {
  state = {
    open: false,
    location: "",
    work_description: "",
    date_of_commencement: new Date(),
    date_of_completion: new Date(),
    status_of_work: "",
    vertical: "top",
    horizontal: "center"
  };

  handleCommencementDateChange = date => {
    this.setState({
      date_of_commencement: date
    });
  };

  handleCompletionDateChange = date => {
    this.setState({
      date_of_completion: date
    });
  };

  handleToggle = () => {
    this.setState({
      open: !this.state.open
    });        
  };

  handleClickOpen = () => {
    this.setState({ open: true });
  };

  handleClose = () => {
    this.props.history.push("/dashboard/developmentworks");
  };

  onChange = e => {
    const state = this.state;
    state[e.target.name] = e.target.value;
    this.setState(state);
  };

  handleFormSubmit = e => {
    e.preventDefault();
    const {
      location,
      work_description,
      date_of_commencement,
      date_of_completion,
      status_of_work
    } = this.state;
    axios
      .post("/api/developmenties/", {
        location,
        work_description,
        date_of_commencement,
        date_of_completion,
        status_of_work
      })
      .then(() => {
        // this.props.history.push("/dashboard/developmentworks");
        // window.location.href = window.location.href;
        this.setState({
          open: false,
          vertical: "top",
          horizontal: "center"
        });
      })
      .catch(error => {
        alert("Ooops something wrong happened, please try again");
      });
  };

  handleCancel = () => {
    this.setState({ open: false });
  };

  render() {
    const { classes } = this.props;
    const {
      location,
      work_description,
      date_of_commencement,
      date_of_completion,
      status_of_work,
      vertical,
      horizontal
    } = this.state;

return (
  <MuiThemeProvider theme={theme}>
    <MuiPickersUtilsProvider utils={DateFnsUtils}>
      <div>
        <MuiThemeProvider theme={theme}>
          <Dialog open={this.state.open} onClose={this.handleToggle}>
            <DialogContent required>
              <form onSubmit={this.handleFormSubmit}>
                <TextField
                  value={location}
                  onChange={e =>
                    this.setState({
                      location: e.target.value
                    })
                  }
                  error={location === ""}
                  helperText={
                    location === "" ? "Please enter Location" : " "
                  }
                  label="Location"
                  type="email"
                  fullWidth
                />
                <TextField
                  value={work_description}
                  onChange={e =>
                    this.setState({
                      work_description: e.target.value
                    })
                  }
                  error={work_description === ""}
                  helperText={
                    work_description === ""
                      ? "Please enter Work Description"
                      : " "
                  }
                  label="Description of Work"
                  type="email"
                  fullWidth
                />
                <div>
                  <DatePicker
                    format="dd/MM/yyyy"
                    label="Date of Commencement"
                    value={date_of_commencement}
                    onChange={this.handleCommencementDateChange}
                    disableOpenOnEnter
                    animateYearScrolling={false}
                  />
                </div>
                <div>
                  <DatePicker
                    format="dd/MM/yyyy"
                    label="Date of Completion"
                    value={date_of_completion}
                    onChange={this.handleCompletionDateChange}
                  />
                </div>
                <TextField
                  value={status_of_work}
                  onChange={e =>
                    this.setState({
                      status_of_work: e.target.value
                    })
                  }
                  error={location === ""}
                  helperText={
                    status_of_work === ""
                      ? "Please enter Status of Work!"
                      : " "
                  }
                  label="Status of Work"
                  type="email"
                  fullWidth
                />
              </form>
            </DialogContent>
            <DialogActions>
              <Button
                onClick={this.handleCancel}
                classes={{
                  root: classes.root
                }}
                variant="contained"
              >
                Cancel
              </Button>
              <Button
                onClick={this.handleFormSubmit}
                color="primary"
                variant="contained"
              >
                Save
              </Button>
            </DialogActions>
          </Dialog>
        </MuiThemeProvider>
      </div>
    </MuiPickersUtilsProvider>
  </MuiThemeProvider>
);
  }
}

标签: javascriptreactjs

解决方案


问题是您没有将状态视为不可变的(如 React 文档所建议的那样)。当您调用 时this.setState({ allDevelopmentWorks: res.data }),您将allDevelopmentWorks用一个新的数组的值替换一个新的对象引用。因为数组引用不匹配,直接检查相等会失败(即this.state.allDevelopmentWorks !== prevState.allDevelopmentWorks比较对象引用)。

查看此答案以更新状态数组而不改变它。

并查看loadashisEqual以比较数组相等性。


推荐阅读