14

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??
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
noobie
  • 751
  • 4
  • 13
  • 33
  • Why do you have a axios request in the componentDidUpdate, that axios request will be executed on every render and further because it does a setState it goes on loop – Shubham Khatri Sep 13 '17 at 11:03
  • 1
    You are setting state this causes axios call and that sets state and that causes component to update and that sets state and that causes update and that sets state and that causes update and that sets state and that causes update and that sets state and that causes update and that sets state and that causes update and that sets state and that causes update and that sets state.... – bennygenel Sep 13 '17 at 11:05
  • @ShubhamKhatri where is `setState()` inside axios?? axios is calling `setData()` which checks equality before executing `setState()` – noobie Sep 13 '17 at 11:22
  • You are calling `this.setData(response.data);` in axios success callback, that has setState – Shubham Khatri Sep 13 '17 at 11:24
  • @ShubhamKhatri setData() also have `if(this.state.data != data.data)` condition. See the commented portions those value's are equal. Then also it is getting executed. – noobie Sep 13 '17 at 11:25
  • @bennygenel axios doesn't directly do `setState()` instead it calls `setData()` which checks for equality before executing `setState()` – noobie Sep 13 '17 at 11:28
  • Whats the response data? is it a string? – bennygenel Sep 13 '17 at 11:32
  • @bennygenel `response.data` is array of json objects – noobie Sep 13 '17 at 11:35
  • You can't just compare 2 object arrays with equality operators. Check [this answer](https://stackoverflow.com/a/27212/2315280) on how to do it and please check [shouldComponentUpdate](https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate) event of React – bennygenel Sep 13 '17 at 11:39

1 Answers1

17

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
  }
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
  • Ok. Then please tell me what should I do? – noobie Sep 13 '17 at 11:43
  • I've also tried comparing two strings `if(this.state.url != this.props.url)` (Commented in the code) it doesn't work either. Although during infinite loop, when I log their o/p they are exactly same. – noobie Sep 13 '17 at 11:50
  • @Sag1v What alternative from calling ajax in `componentWillMount` do you suggest? I am doing this most of the time but I am not sure that it is the proper way. Perhaps you could include it to your answer too – JD Hernandez Sep 15 '17 at 03:38
  • @JDHrnnts ajax requests should be invoked in `componentDidMount` (answer updated). [DOCS](https://facebook.github.io/react/docs/react-component.html#componentdidmount) – Sagiv b.g Sep 15 '17 at 04:59