bdart
bdart

Reputation: 755

react componentdidupdate provokes infinite loop

I trigger an API call to elasticsearch with onChange so that I can prompt a list for autocomplete. To ensure that my store was updated before rerender I added componentDidMount so that I am not one tick behind. But getting to the point needs code, so:

constructor(props) {
  super(props)
  this.state = {
    inputValue: '',
    options: []
  };
  this.props = {
    inputValue: '',
    options: []
  };
  this._onChange = this._onChange.bind(this);
}

componentDidMount() {
  CodeStore.addChangeListener(this._onChange);
}

_onChange(event) {
  if (this.state.inputValue && !event) {
    var enteredChars = this.state.inputValue;
    console.log('NO EVENT!');
  } else if (event.target.value) {
    console.log('EVENT!');
    var enteredChars = event.target.value;
    this.setState({inputValue: enteredChars});
  }
  CodeService.nextCode(enteredChars);
}

As you can see I added some log events just to get sure my condition is doing right. I read about that setState provokes a rerender so it is inside the condition but that didn't had stopped the loop . And the console log confirms the condition switch. But having the setState inside my condition brakes the functionality and I do not get a list.

Here is my log:

                        0
Home.jsx:48             EVENT!
Home.jsx:50             0
CodeService.js:27       request
CodeActions.js:10       dispatch
CodeStore.js:22         store
Home.jsx:43             1
Home.jsx:46             NO EVENT!
10OptionTemplate.jsx:15 render-list
CodeService.js:27       request
CodeActions.js:10       dispatch
CodeStore.js:22         store
Home.jsx:43             1
Home.jsx:46             NO EVENT!
10OptionTemplate.jsx:15 render-list
CodeService.js:27       request
CodeActions.js:10       dispatch
CodeStore.js:22         store
Home.jsx:43             1
Home.jsx:46             NO EVENT!

The infinity loop does not hurt the performance anyhow. I think because of componentWillUnmount but the massive amount of API calls have to be avoided. Hope this is enough information for any evidence.

Upvotes: 3

Views: 4769

Answers (2)

wintvelt
wintvelt

Reputation: 14101

Looks like the infinite loop is caused by the following pattern:

  1. user types input
  2. _onChange updates state + fires action to store
  3. Store updates itself and send change event
  4. Store change event fires same _onChange event
  5. _onChange does not update state, but fires action to store nonetheless
  6. Goto step 3. and repeat (indefinitely)

Steps to take to fix:

  • make different change handlers for user input and store update (as already suggested by @Janaka Stevens)
  • put the data from store in state
  • user input does not have to be in state (if your react code does not provide a value to an input field, it will start with empty value, and leave whatever is typed alone)

Then your code could look something like this:

constructor(props) {
  super(props)
  this.state = {
    options: []
  };
  // removed the this.props bit: props should not be updated inside component
  this._onChangeUser = this._onChangeUser.bind(this);
  this._onChangeStore = this._onChangeStore.bind(this);
}

componentDidMount() {
  CodeStore.addChangeListener(this._onChange);    // this is OK
}

_onChangeUser(event) {
  console.log('EVENT!');
  var enteredChars = event.target.value;
  CodeService.nextCode(enteredChars);
  // No need to update state here - go to sleep until store changes
}

_onChangeStore() {
  var newList = getListFromStore();  // your own function to get list from store
  this.setState({ options: newList});            // update state here
}

Not sure what you meant by 'one tick behind', or what could have caused it, but cleaning up endless loop is a good place to start ;)

PS: Code above is sketch only, may have typo's.

Upvotes: 2

J. Mark Stevens
J. Mark Stevens

Reputation: 4945

It looks like you are using _onChange for both your input event and store listener. They need separate methods.

Upvotes: 1

Related Questions