Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React infinite loop using componentDidUpdate

I am trying to create a basic Wizard, with 3 steps. In my root file, I setup my state in the constructor:

constructor(props) {
    super(props);
    this.state = {
      currentStep: 1,
      data: {
        firstname: 'James',
        surname: 'Smith',
        country: 'Australia',
      },
    };

My Wizard has 3 'steps', and here's an example of how I render one:

<DataEntry currentStep={this.state.currentStep} data={this.state.data} afterValidate={this._nextStep} moveBack={this._prevStep} />

And then in the step, my constructor is:

  constructor(props) {
    super(props);
    this.state = { data: this.props.data };
    this._validate = this._validate.bind(this);
    console.log(this.state);
  }

So I set the step's state based on the props I pass in as 'data'.

My onChange is generic, so each 'field' has an onchange, that does this:

  onChange(fieldName, fieldValue) {
    const thisData = { ...this.state.data, [fieldName]: fieldValue };
    this.setState({ data: thisData });
  }

When I move to the next step with my 'Next' button, I call a method in my 'step' component:

  _validate() {
    console.log('Validating...');
    this.props.afterValidate(this.state.data);
  }

This calls the method in my parent component, to update the data:

  _nextStep(data) {
    console.log('Next');
    let currentStep = this.state.currentStep;
    console.log(currentStep);
    currentStep = currentStep >= 2 ? 3 : currentStep + 1;
    this.setState({ currentStep, data }, ()=>{console.log('New state: ', this.state)});
  }

And this work. But this issue is that I seem to get stuck in a loop, because I need to update the state in my other 'step' components. Each Step has:

  componentDidUpdate() {
    console.log('Data Entry got Prop Update...');
    this.setState({ data: this.props.data });
  }

So, when the state in the parent updates, it needs to update the state in the Steps... But I seem to get into a violent loop:

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

I'm guessing that 'componentDidUpdate' is being called for them all, because the parent state basically updated all the steps, and they in turn, inform the parent that the state updated?

What is the right way to achieve this? Basically, I want the datat in the parent to be the source of truth, and then updates in each 'step' updates this state, and propogates to the steps.

like image 791
Craig Avatar asked Oct 07 '18 00:10

Craig


1 Answers

This issue had been described in React Document:

componentDidUpdate(prevProps) {
  // Typical usage (don't forget to compare props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

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.

For more information: componentDidUpdate()

In addition, in order to compare the prevProps and the currentProps, you might think about the Object Comparison in Javascript.

There are several ways to compare two objects in javascript. For example:

1) Fast and limited approach

Works when you have simple JSON-style objects without methods and DOM nodes inside:

JSON.stringify(obj1) === JSON.stringify(obj2)

The ORDER of the properties IS IMPORTANT, so this method will return false for following objects:

 x = {a: 1, b: 2};
 y = {b: 2, a: 1};

This example is taken from: Object comparison in JavaScript

like image 87
You Nguyen Avatar answered Nov 10 '22 16:11

You Nguyen