Search code examples
javascriptjsonreactjsinfinite-loop

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

Solution

  • 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;