简体   繁体   中英

React setState inside componentDidUpdate causing infinite loop

Can someone help me solve how do I setState inside componentDidUpdate and not have an infinite loop? Some suggestions said to have a conditional statement, but I am not too familiar with how do I set the conditional for my code.

This is what my code looks like:

I have a dashboard component that gets all the companies and projects data from external functions where the fetch happens and then updates the state. The projects are associated with the company's id.

I am able to get the list of all the projects in JSON, but I can't figure out how to update my projects state inside componentDidUpdate once rendered.

CompanyDashboard.js

import { getCompanys } from "../../actions/companyActions";
import { getProjects } from "../../actions/projectActions";

class CompanyDashboard extends Component {
  constructor(props) {
    super(props);
    this.state = {
      companies: [],
      projects: []
    };
  }

  componentWillMount() {
    // get all companies and update state
    getCompanys().then(companies => this.setState({ companies }));
  }

  componentDidUpdate(prevState) {
    this.setState({ projects: this.state.projects });
  }

  render() {
    const { companies, projects } = this.state;
    {
      companies.map(company => {
        // get all the projects
        return getProjects(company);
      });
    }
    return <div />;
  }
}

export default CompanyDashboard;

companyActions.js

import { getUser, getUserToken } from './cognitoActions';
import config from '../../config';

export function getCompanys() {
    let url = config.base_url + '/companys';
    return fetch(url, {
      method: 'GET',
      headers: {'token': getUserToken() }
    })
    .then(res => res.json())
    .then(data => { return data })
    .catch(err => console.log(err));
}

projectActions.js

import { getUserToken } from './cognitoActions';
import config from '../../config';

export function getProjects(company) {
  let url = config.base_url + `/companys/${company._id['$oid']}/projects`;
  return fetch(url, {
    method: 'GET',
    headers: {'token': getUserToken() }
  })
  .then(res => res.json())
  .then(data => { return data })
  .catch(err => console.log(err));
}

componentDidUpdate has this signature, componentDidUpdate(prevProps, prevState, snapshot)

This means that every time the method gets called you have access to your prevState which you can use to compare to the new data, and then based on that decide if you should update again. As an example it can look something like this.

componentDidUpdate(prevProps, prevState) {
  if (!prevState.length){
    this.setState({ projects: this.state.projects })
  }
}

Of course this is only an example since I don't know your requirements, but this should give you an idea.

The following code is not doing anything meaningful. You are setting your state.projects to be equal to your state.projects.

  componentDidUpdate() {
    this.setState({ projects: this.state.projects })
  }

Also, the following code is not doing anything meaningful because you are not saving the result of companies.map anywhere.

    {
      companies.map((company) => {
        return getProjects(company) 
      })
    } 

It's hard to tell what you think your code is doing, but my guess is that you think that simply calling "companies.map(....) " inside your render function is going to TRIGGER the componentDidUpdate function. That is not how render works, you should go back to the drawing board on that one. It also looks like you think that using the curly brackets {} inside your render function will display the objects inside your curly brackets. That's also not true, you need to use those curly brackets inside the components. For instance: {projects}

If I had to guess... the following code is how you actually want to write your component

import { getCompanys } from '../../actions/companyActions';
import { getProjects } from '../../actions/projectActions';

class CompanyDashboard extends Component {
  constructor(props) {
    super(props);
    this.state = {
      companies: [],
      projects: []
    }
  }

  componentWillMount() {
    getCompanys().then(companies => {
      const projectPromises = companies.map((company) => {
        return getProjects(company) 
      });

      Promise.all(projectPromises).then(projects => {
        //possibly a flatten operator on projects would go here.

        this.setState({ companies, projects });
      });


      /*
       * Alternatively, you could update the state after each project call is returned, and you wouldn't need Promise.all, sometimes redux can be weird about array mutation in the state, so look into forceUpdate if it isn't rerendering with this approach:
       * const projectPromises = companies.map((company) => {
       *   return getProjects(company).then(project => this.setState({projects: this.state.projects.concat(project)}));
       * });
       */

    )
  }

  render() {
    const { companies, projects } = this.state;

    //Not sure how you want to display companies and projects, but you would 
    // build the display components, below.
    return(
      <div>
         {projects}
      </div>
    )
  }

}

export default CompanyDashboard;

When componentDidUpdate() is called, two arguments are passed: prevProps and prevState . This is the inverse of componentWillUpdate() . The passed values are what the values were, and this.props and this.state are the current values.

`componentDidUpdate(prevProps) {
    if (this.props.userID !== prevProps.userID) {
        this.fetchData(this.props.userID);
    }
}`

You must check the state/props if new state/props different from previous one then you can allow to update your component.

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you'll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance. If you're trying to “mirror” some state to a prop coming from above, consider using the prop directly instead.

This is because componentDidUpdate is called just after a component takes up somechanges in the state. so when you change state in that method only then it will move to and from from that method and state change process

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM