Javier
Javier

Reputation: 2095

React infinite loop

I am new to React and sorry for my English.

When I pass data from Child to parent component and click on another button it doesn't render.

as you can see in my comment setState creates an infinite loop stopping the option to update the value.

I have tried different ways to solve the problem creating inside constructor variables like this.name = '' and this.selected = [], and setting on callback as this.name = name and this.selected = selected but doesn't render when I click in other options.

I've tried work with componentDidMount() and other methods of life cycle but I don't know how to solve it.

App.js (Parent component)

state = { name: '', selected: [] };

doParentControl = (name, selected) => {
   console.log('doParentControl name: ',name);
   console.log('doParentControl selected: ',selected);
   // this.setState({ name: name, selected: selected }) --> infinite loop
}

render() {
   console.log('-> render App')
   return (
    <div className="App">
      <header className="App-header">
        <h1>Map</h1>
       </header>
       <div className="Map">
         <Navbar parentControl={this.doParentControl}/>
         <MapComponent name={this.state.name} selected={this.state.selected}/>
       </div>
       <div className="clearfix" />
   </div>
   );
}

Navbar.js (Child component)

constructor(props) {
    super(props);
    this.selected = [false, false, false];
    this.state = { name: 'tile' };
}

handleClick = (e) => {
    this.setState({name: e.target.value});
}

handleChange = (e) => {
    switch(e.target.value) {
        case 'option1': {
            this.setState({ selected: this.selected[0] = e.target.checked})
            break;
        }
        case 'option2': {
            this.setState({ selected: this.selected[1] = e.target.checked})        
            break;
        }
        case 'option3': {
            this.setState({ selected: this.selected[2] = e.target.checked})        
            break;
        }
        default:
            break;
    }
}

doParentControlFromChild = () => {
    this.props.parentControl(this.state.name, this.selected);
}

render() {
    console.log('render Navbar ->')
    return (
        <div className="nav-bar">
            <nav className="App-nav">
                <div className="buttons">
                    <button name="tile"        onClick={this.handleClick} value="tile">Tile</button>
                    <button name="tile-watercolor" onClick={this.handleClick} value="tile-watercolor">Tile WaterColor</button>
                    <button name="cartografia" onClick={this.handleClick} value="cartografia">Cartografía</button>
                    <button name="satelite"    onClick={this.handleClick} value="satelite">Satélite</button>
                    <button name="mapa"        onClick={this.handleClick} value="mapa">Mapa</button>
                </div>
                <div className="clearfix"></div>
            </nav>
            <div className="checkbox">
                <label><input type="checkbox" name="option1" value="option1"  onChange={this.handleChange} />Option 1</label>
                <label><input type="checkbox" name="option2" value="option2"   onChange={this.handleChange} />Option 2</label>
                <label><input type="checkbox" name="option3" value="option3"      onChange={this.handleChange} />Option 3</label>
            </div>
            <div className="clearfix" />
            { this.doParentControlFromChild() }
        </div>
    );
}

Upvotes: 0

Views: 2560

Answers (2)

Carr
Carr

Reputation: 2771

setState() let render() function re-render.
So setState() deployed at doParentControl in your parent component causes component re-rendering, and in your child component, return( ... { this.doParentControlFromChild()} ... ) just accompany with elements rendering. This cause doParentControl from your parent component be called again, so here we have the infinity-loop.
Just like earlier answer said, you need to let doParentControlFromChild be called only when it need to happen.

App.js (Father)
...
state = { name: '', selected: [] };

doParentControl = (name, selected) => {
   console.log('doParentControl name: ',name);
   console.log('doParentControl selected: ',selected);
   this.setState({ name: name, selected: selected })
}

render() {
   console.log('-> render App')
   return (
    <div className="App">
      <header className="App-header">
        <h1>Map</h1>
       </header>
       <div className="Map">
         <Navbar parentControl={this.doParentControl}/>
         <MapComponent name={this.state.name} selected={this.state.selected}/>
       </div>
       <div className="clearfix" />
   </div>
   );
}
...

Navbar.js (Child)
...

constructor(props) {
    super(props);
    this.selected = [false, false, false];
    this.state = { name: 'tile' };
}

handleClick = (e) => {
    this.setState({name: e.target.value});
}

handleChange = (e) => {

     let index = e.target.value.match(/\d/); // fetch the idx
     this.setState({ selected: this.selected[index] = e.target.checked}, () => {  
        this.doParentControlFromChild()
     })

}

doParentControlFromChild = () => {
    this.props.parentControl(this.state.name, this.selected);
}

render() {
    console.log('render Navbar ->')
    return (
        <div className="nav-bar">
            <nav className="App-nav">
                <div className="buttons">
                    <button name="tile"        onClick={this.handleClick} value="tile">Tile</button>
                    <button name="tile-watercolor" onClick={this.handleClick} value="tile-watercolor">Tile WaterColor</button>
                    <button name="cartografia" onClick={this.handleClick} value="cartografia">Cartografía</button>
                    <button name="satelite"    onClick={this.handleClick} value="satelite">Satélite</button>
                    <button name="mapa"        onClick={this.handleClick} value="mapa">Mapa</button>
                </div>
                <div className="clearfix"></div>
            </nav>
            <div className="checkbox">
                <label><input type="checkbox" name="option1" value="option1"  onChange={this.handleChange} />Option 1</label>
                <label><input type="checkbox" name="option2" value="option2"   onChange={this.handleChange} />Option 2</label>
                <label><input type="checkbox" name="option3" value="option3"      onChange={this.handleChange} />Option 3</label>
            </div>
            <div className="clearfix" />
        </div>
    );
}

Upvotes: 1

jmargolisvt
jmargolisvt

Reputation: 6088

{ this.doParentControlFromChild() } is making a function call upon render, which causes a state change, which causes a re-render, hence your loop. Get rid of the parens and only allow a function that calls setState to be called from a user action, not simply a render call. Something like this will do:

<div className="clearfix" onClick={this.doParentControlFromChild}/>

Upvotes: 1

Related Questions