How to avoid the setState() / render() endless loop when passing data from child to parent?

Jen Source

I am trying to save child-data in the state of the parent, but end up with the endless loop because setState() calls render().

Error message: 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.

Relatively new to React, so I can't seem to word the question when googling solutions. I know why the error is occurring, I just don't know how to get around the issue. Is there a specific method I can use that prevents re-rendering?

Here is the parent:

export class ToDoList extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      data: null
    }
  }

  myCallback = (dataFromChild) => {
    this.setState({data: dataFromChild.toUpperCase()})
  }

  render() {
    return (
      <div>
        <ToDoItem callbackFromParent={this.myCallback}/>
      </div>
    );
  }
}

The child:

class ToDoItem extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      listInfo: 'Doggos'
    }
  }

  render(){
    return(
      <h1>{this.props.callbackFromParent(this.state.listInfo)}</h1>
    );
  }
}
javascriptreactjs

Answers

answered 2 months ago Chase DeAnda #1

Is this what you are trying to achieve?

class ToDoList extends React.Component {

  toUpper = (dataFromChild) => {
    return dataFromChild.toUpperCase();
  }

  render() {
    return (
      <div>
        <ToDoItem toUpper={this.toUpper}/>
      </div>
    );
  }
}

class ToDoItem extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      listInfo: 'Doggos'
    }
  }

  render(){
    return(
      <h1>{this.props.toUpper(this.state.listInfo)}</h1>
    );
  }
}

ReactDOM.render(<ToDoList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

You are getting an endless loop because of the following reasons:

1) You are calling the parent callback on each render.

2) You are saving the uppercased value in the parent state.

When the parent state gets updated, the child gets re-rendered, meaning that it will call the callback again, which will cause to re-render, which calls the callback again etc...

An alternative solution would be to pass the util function down to the child which can then call it once when it re-renders. Since no state in the parent is being updated, the child will not be re-rendered.

answered 2 months ago Daniel Pendergast #2

Your code is doing exactly that, an endless loop. When your ToDoItem component renders, it calls callbackFromParent which updates the state of ToDoList, causing ToDoList to re-render, subsequently re-rendering the ToDoItem. Since ToDoItem re-renders, it calls callbackFromParent again and so on...

I'd like to ask why you are trying to render the non-value-returning function of callbackFromParent. It doesn't return anything, so it doesn't make sense why you'd want to render it inside of your <h1> tags.

answered 2 months ago Gabriel Balsa CantĂș #3

If you're trying to save data on parent but want to display it in child, try this:

Here is the parent:

export class ToDoList extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      data: null
    }
  }

  myCallback = (dataFromChild) => {
    this.setState({data: dataFromChild})
  }

  render() {
    return (
      <div>
        <ToDoItem callbackFromParent={this.myCallback} data={this.state.data}/>
      </div>
    );
  }
}

The child:

class ToDoItem extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      times: 0
    }
    // Bind Explained below
    this.iBeClicked = this.iBeClicked.bind(this);
  }

  iBeClicked(){
    this.setState({times: ++this.props.data});
    this.props.callbackFromParent(this.props.data++);
  }

  render(){
    return(
      <div className="wrap">
        <h1 onClick="iBeClicked">{this.props.data !== null ? this.props.data: 'Nothing' }</h1>
      </div>
    );
  }
}

You use this.method.bind(this) in order to bind Component's this to React callback's execution inside render.

answered 2 months ago gandhi_rahul #4

There is a small problem with the code you shared, that you are calling a function from the render() rather than binding it to some event which is making it go into infinite loop...

enter image description here

comments powered by Disqus