2

Is there any way to make sure render is called after setState actually updates the state? From my knowledge, setState is asynchronous. I have tried putting render as the callback to setState, but it still renders before the state is updated.

HTML code:

<div id="root"></div>

React code:

class Table extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            squares: []
        };
        this.create = this.create.bind(this);
    }

    create() {
        console.log('squares before: ' + this.state.squares);
        var a = [];
        for (var i = 0; i < 16; i++) {
            a.push(undefined);
        }
        i = Math.floor(Math.random() * 8);
        a[i] = 2;
        i = Math.floor(Math.random() * 8) + 8;
        a[i] = 2;
        console.log('new squares: ' + squares);
        console.log('non-updated squares: ' + this.state.squares);
        this.setState({
            squares: a
        }, console.log('updated squares: ' + this.state.squares);
    }

    componentWillMount() {
        this.create();
    }

    render() {
        console.log('rendering');
        return (<div>
                <table className="table">
                    <tbody>
                        <tr>
                            <td className="td"><Number number={this.state.squares[0]}/></td>
                            <td className="td"><Number number={this.state.squares[1]}/></td>
                            <td className="td"><Number number={this.state.squares[2]}/></td>
                            <td className="td"><Number number={this.state.squares[3]}/></td>
                        </tr>
                        <tr>
                            <td className="td"><Number number={this.state.squares[4]}/></td>
                            <td className="td"><Number number={this.state.squares[5]}/></td>
                            <td className="td"><Number number={this.state.squares[6]}/></td>
                            <td className="td"><Number number={this.state.squares[7]}/></td>
                        </tr>
                        <tr>
                            <td className="td"><Number number={this.state.squares[8]}/></td>
                            <td className="td"><Number number={this.state.squares[9]}/></td>
                            <td className="td"><Number number={this.state.squares[10]}/></td>
                            <td className="td"><Number number={this.state.squares[11]}/></td>
                        </tr>
                        <tr>
                            <td className="td"><Number number={this.state.squares[12]}/></td>
                            <td className="td"><Number number={this.state.squares[13]}/></td>
                            <td className="td"><Number number={this.state.squares[14]}/></td>
                            <td className="td"><Number number={this.state.squares[15]}/></td>
                        </tr>
                    </tbody>
                </table>
            </div>);
    }
}

class Game extends React.Component {
    newGame() {
        this.refs.table.create();
    }

    render() {
        return (<div>
            <Table ref="table"/>
            <button id="newGame" onClick={this.newGame.bind(this)}>New Game</button>
        </div>);
    }
}

Basically, what happens in this is that 'rendering' gets logged first and then 'updated squares: ...' like this:

squares before: [undefined, 2, undefined, undefined, undefined, 2]
new squares: [undefined, undefined, 2, undefined, 2, undefined]
non-updated squares: [undefined, 2, undefined, undefined, undefined, 2]
rendering
updated squares: [undefined, undefined, 2, undefined, 2, undefined]

I tried making the callback to setState render, but the second render would render after the first render but before setState updated state:

this.setState({
  squares: a
}, this.render);

It logged:

squares before: [undefined, 2, undefined, undefined, undefined, 2]
new squares: [undefined, undefined, 2, undefined, 2, undefined]
non-updated squares: [undefined, 2, undefined, undefined, undefined, 2]
rendering
rendering

Any help would be greatly appreciated. Thanks in advance!

Vappor Washmade
  • 255
  • 3
  • 14

2 Answers2

2

A synchronous setState in componentWillMount doesn't trigger a re-render instead reflects the change in the initial render itself.The callback to setState however is called after the render method.

If you log the state squares inside the render method, it will log the correct result in the initial render itself.

P.S. componentWillMount is deprecated in the latest versions and its better to have your current logic in constructor instead of componentWillMount

Working demo

Shubham Khatri
  • 211,155
  • 45
  • 305
  • 318
  • But how do you fix the code so that it renders after setState is called? – Vappor Washmade Mar 14 '19 at 05:54
  • @VapporWashmade,you present code will call render only after setState is called and updates state, only the callback to setState is triggered afterwards – Shubham Khatri Mar 14 '19 at 06:04
  • @ShubhamKhatri I have the a similar question. Is there a precondition that the `render` method will only be called after the state really been changed. – hiveer Nov 06 '19 at 08:57
0

use shouldComponentUpdate(object nextProps, object nextState), for render, each time it returns true, render will happen