¿Puedo ejecutar una función después de que setState haya terminado de actualizar?

175

Soy muy nuevo en React JS (como en, recién comencé hoy). No entiendo cómo funciona setState. Estoy combinando React y Easel JS para dibujar una cuadrícula basada en la entrada del usuario. Aquí está mi bin JS: http://jsbin.com/zatula/edit?js,output

Aquí está el código:

        var stage;

    var Grid = React.createClass({
        getInitialState: function() {
            return {
                rows: 10,
                cols: 10
            }
        },
        componentDidMount: function () {
            this.drawGrid();
        },
        drawGrid: function() {
            stage = new createjs.Stage("canvas");
            var rectangles = [];
            var rectangle;
            //Rows
            for (var x = 0; x < this.state.rows; x++)
            {
                // Columns
                for (var y = 0; y < this.state.cols; y++)
                {
                    var color = "Green";
                    rectangle = new createjs.Shape();
                    rectangle.graphics.beginFill(color);
                    rectangle.graphics.drawRect(0, 0, 32, 44);
                    rectangle.x = x * 33;
                    rectangle.y = y * 45;

                    stage.addChild(rectangle);

                    var id = rectangle.x + "_" + rectangle.y;
                    rectangles[id] = rectangle;
                }
            }
            stage.update();
        },
        updateNumRows: function(event) {
            this.setState({ rows: event.target.value });
            this.drawGrid();
        },
        updateNumCols: function(event) {
            this.setState({ cols: event.target.value });
            this.drawGrid();
        },
        render: function() {
            return (
                <div>
                    <div className="canvas-wrapper">
                        <canvas id="canvas" width="400" height="500"></canvas>
                        <p>Rows: { this.state.rows }</p>
                        <p>Columns: {this.state.cols }</p>
                    </div>
                    <div className="array-form">
                        <form>
                            <label>Number of Rows</label>
                            <select id="numRows" value={this.state.rows} onChange={ this.updateNumRows }>
                                <option value="1">1</option>
                                <option value="2">2</option>
                                <option value ="5">5</option>
                                <option value="10">10</option>
                                <option value="12">12</option>
                                <option value="15">15</option>
                                <option value="20">20</option>
                            </select>
                            <label>Number of Columns</label>
                            <select id="numCols" value={this.state.cols} onChange={ this.updateNumCols }>
                                <option value="1">1</option>
                                <option value="2">2</option>
                                <option value="5">5</option>
                                <option value="10">10</option>
                                <option value="12">12</option>
                                <option value="15">15</option>
                                <option value="20">20</option>
                            </select>
                        </form>
                    </div>    
                </div>
            );
        }
    });
    ReactDOM.render(
        <Grid />,
        document.getElementById("container")
    );

Puede ver en el contenedor JS cuando cambia el número de filas o columnas con uno de los menús desplegables, no pasará nada la primera vez. La próxima vez que cambie un valor desplegable, la cuadrícula dibujará los valores de fila y columna del estado anterior. Supongo que esto está sucediendo porque mi función this.drawGrid () se está ejecutando antes de que setState se complete. Tal vez hay otra razón?

¡Gracias por tu tiempo y ayuda!

monalisa717
fuente

Respuestas:

450

setState(updater[, callback]) es una función asíncrona:

https://facebook.github.io/react/docs/react-component.html#setstate

Puede ejecutar una función después de que setState termine utilizando el segundo parámetro callbackcomo:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});
sytolk
fuente
33
o simplemente this.setState ({someState: obj}, this.afterSetStateFinished);
Aleksei Prokopov
Quiero añadir que en mi ejemplo específico, probablemente quiera llamar this.drawGrid()en componentDidUpdatecomo @kennedyyu sugirió, porque cada vez que setStatese termina, me va a querer volver a dibujar la red (por lo que esto ahorraría algunas líneas de código), pero lograr la misma cosa .
monalisa717
Gran respuesta ... me ayudó mucho
MoHammaD ReZa DehGhani
Me ahorra mucho tiempo. Gracias.
Nayan Dey
28

renderse le llamará cada vez que setStatevuelva a representar el componente si hay cambios. Si mueve su llamada drawGridallí en lugar de llamarla en sus update*métodos, no debería tener ningún problema.

Si eso no funciona para usted, también hay una sobrecarga setStateque toma una devolución de llamada como segundo parámetro. Debería poder aprovechar eso como último recurso.

Justin Niessner
fuente
2
Gracias, su primera sugerencia funciona y (en su mayoría) tiene sentido. Hice esto: render: function() { this.drawGrid(); return......
monalisa717
3
Ejem, por favor no hagas esto en render()... la respuesta de sytolk debería ser la aceptada
mfeineis
Esta es una respuesta incorrecta, setState buscará un bucle infintie y bloqueará la página.
Maverick
Justin, "me interesa por qué dijo que la devolución de llamada de setStatedebe utilizarse un último recurso estoy de acuerdo con la mayoría de la gente aquí que tiene sentido utilizar este enfoque..
monalisa717
1
@Rohmer es demasiado costoso de ejecutar en cada llamada de renderizado, aunque obviamente tampoco es necesario en todas las llamadas. Si fuera puro, reactel Vdom se encargaría de no hacer demasiado trabajo en la mayoría de los casos, esto es interoperabilidad con otra biblioteca que desea minimizar
mfeineis
14

Haciendo setStatevolver aPromise

Además de pasar una callbackal setState()método, se puede envolver alrededor de una asyncfunción y utilizar el then()método - que en algunos casos puede producir un código más limpio:

(async () => new Promise(resolve => this.setState({dummy: true}), resolve)()
    .then(() => { console.log('state:', this.state) });

Y aquí puede dar un paso más y hacer una setStatefunción reutilizable que, en mi opinión, es mejor que la versión anterior:

const promiseState = async state =>
    new Promise(resolve => this.setState(state, resolve));

promiseState({...})
    .then(() => promiseState({...})
    .then(() => {
        ...  // other code
        return promiseState({...});
    })
    .then(() => {...});

Esto funciona bien en React 16.4, pero no lo he probado en versiones anteriores de React .

También vale la pena mencionar que mantener el código de devolución de llamada en el componentDidUpdatemétodo es una mejor práctica en la mayoría, probablemente en todos los casos.

Mahdi
fuente
11

cuando se reciben nuevos accesorios o estados (como se llama setStateaquí), React invocará algunas funciones, que se llaman componentWillUpdateycomponentDidUpdate

en su caso, simplemente agregue una componentDidUpdatefunción para llamarthis.drawGrid()

aquí está el código de trabajo en JS Bin

como mencioné, en el código, componentDidUpdatese invocará despuésthis.setState(...)

entonces componentDidUpdateadentro va a llamarthis.drawGrid()

Lea más sobre el ciclo de vida del componente en React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

Kennedy Yu
fuente
en lugar de simplemente pegar el enlace. por favor agregue las porciones relevantes en la respuesta.
Phoenix