Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does componentDidUpdate() create an infinite loop?

I've stored url and a token in state in Parent component. I'm passing an url and a token as props from parent Component to child Component. However, if there is some event in parent Component, setState() is triggered and as a result, componentDidUpdate() of child Component gets executed.
As componentDidUpdate() was causing an infinite loop (as it triggers setState() inside child component), I've placed condition. But this does not prevent the error.
Child Component ie DisplayRevenue is as follows:

import React, { Component } from 'react';
import '../App.css';
import ListData from './listdata.js'
var axios = require('axios');

class DisplayRevenue extends Component {

  constructor(props){
    super(props);
    this.state = { data:[], url:"" }
  console.log(this.props.url);
  }

  componentWillMount() {
    this.loadRevenue(this.props.url, this.props.token);
 }

  componentDidUpdate(){    //creates infinite loop
  //  console.log(this.props.url);
    this.loadRevenue(this.props.url, this.props.token);
  }

  setData(data){
    //if(this.state.url != this.props.url){
    if(this.state.data != data.data){
      console.log(data.data);                     //(1)
  //    console.log(this.state.url);              //(2)
      this.setState(data:data);             
      console.log(this.state.data);               //(3)
  //    console.log(this.props.url);              //(4)
    }     //(1) & (3) yields exactly same value so does (2) & (4)
  }

  loadRevenue(url,token){
    axios({
      method:'get',
      url:url,
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
     .then( (response) => {
    //   console.log(response.data);
       this.setData(response.data);
     })
     .catch(function (error) {
       console.log("Error in loading Revenue "+error);
     });
  }

  render() {
    return (
      <ListData data={this.state.data}/>
    );
  }
};

export default DisplayRevenue;

Parent Component ie MonthToDate is as below:

import React, { Component } from 'react';
import '../App.css';
import DisplayRevenue from './displayRevenue'
var axios = require('axios');

class MonthToDate extends Component {

  constructor(props){
    super(props);
    this.state = {
      data:null,
      url:"http://localhost:3000/api/monthtodate"
    }
    //console.log(this.props.location.state.token);
  }

  groupBySelector(event){
    if ((event.target.value)==="invoice"){
      this.setState({url:"http://localhost:3000/api/monthtodate"})
    } else if ((event.target.value)==="customer") {
      this.setState({url:"http://localhost:3000/api/monthtodate?group-by=customerNumber"})
    } else if ((event.target.value)==="month") {
      this.setState({url:"http://localhost:3000/api/invoices?group-by=month"})
    } else {
      this.setState({url:"http://localhost:3000/api/monthtodate"})
    }
    console.log(this.state.url);
  }

  render() {
    return (
      <div>
      <select onChange={(event)=>this.groupBySelector(event)}>
        <option value="invoice">GROUP BY INVOICE</option>
        <option value="customer">GROUP BY CUSTOMER</option>
        <option value="month">GROUP BY MONTH</option>
      </select>
        <DisplayRevenue url={this.state.url} token={this.props.location.state.token}/>
      </div>
    );
  }
}

export default MonthToDate;
  • What am I missing?
  • Also, after I've received the url in the child component I want to render different component based on that url. For example <ListData /> component can handle only one type of url. How can I render another component within render() based on the url type??
like image 437
noobie Avatar asked Sep 13 '17 10:09

noobie


People also ask

How do you stop an infinite loop in componentDidUpdate?

Tip: To avoid an infinite loop, all the network requests are needed to be inside a conditional statement as: componentDidUpdate(prevProps, prevState) { if (prevState. data !== this.

Why does componentDidUpdate keep getting called?

The componentDidUpdate function is a part of a React component's life cycle. It is called when a component got updated. This might happen if new props have been provided by a parent component or an internal state has been changed.

What triggers componentDidUpdate?

The componentDidUpdate event is triggered whenever there is a state or props update. ComponentDidUpdate() has access to three properties, two of which are most often used more than the third. These are prevProps, prevState and lastly, snapshot, which is used less often.

What is the difference between componentDidUpdate and componentDidMount?

componentDidMount() : invoked immediately after a component is mounted (inserted into the DOM tree) componentDidUpdate(prevProps, prevState, snapshot) : is invoked immediately after updating occurs. This method is not called for the initial render.


1 Answers

You are calling an ajax call in componentDidUpdate, and you set the state on the callback, that will trigger another call and update which will call the ajax request again and callback will set state again and so on.
Your condition in setData:

if(this.state.data != data.data) 

will always return true as objects are reference type and can't be compared, no matter what data returned from the ajax call it will always be a different object and will return true in your condition. Example:

var obj1 = {a:1}
var obj2 = {a:1}

console.log(obj1 != obj2); // returns true

What you can do, is compare primitives values inside the two objects.
For example:

if(this.state.data.id != data.id) // id could be a string or a number for example

EDIT
Another thing i forgot to mention which may not relate to your problem directly but should be enforced, Never do ajax requests inside componentWillMount or the constructor for that matter, as the render function will be invoked before your ajax request will finish. you can read about it in the DOCS.
Ajax requests should be invoked in componentDidMount life cycle method instead.

EDIT #2
Another thing that can be helpful, in the MonthToDate render function you are passing a new instance of a function on each render (which may cause a performance hit)

<select onChange={(event)=>this.groupBySelector(event)}>

Try changing it to this (the event will be passed automatically to the handler):

 <select onChange={this.groupBySelector}>  

You would also need to bind it in the constructor:

constructor(props){
    super(props);
    this.state = {
      data:null,
      url:"http://localhost:3000/api/monthtodate"
    }
    //console.log(this.props.location.state.token);

    this.groupBySelector = this.groupBySelector.bind(this); // binds this to the class
  }
like image 104
Sagiv b.g Avatar answered Sep 27 '22 21:09

Sagiv b.g