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

Resuelto monalisa717 asked hace 8 años • 10 respuestas

Soy muy nuevo en ReactJS (como recién comenzado hoy). No entiendo muy bien cómo setStatefunciona. Estoy combinando React y Easel JS para dibujar una cuadrícula basada en la entrada del usuario. Aquí está mi contenedor 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 JSbin que cuando cambia el número de filas o columnas con uno de los menús desplegables, no sucederá nada la primera vez. La próxima vez que cambie un valor del menú desplegable, la cuadrícula se basará en los valores de fila y columna del estado anterior. Supongo que esto sucede porque mi this.drawGrid()función se ejecuta antes de setStateque se complete. ¿Quizás haya otra razón?

¡Gracias por tu tiempo y ayuda!

monalisa717 avatar Jan 09 '16 05:01 monalisa717
Aceptado

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 usando el segundo parámetro callbackcomo:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});

Se puede hacer lo mismo con los ganchos en el componente funcional de React:

https://github.com/the-road-to-learn-react/use-state-with-callback#usage

Mire useStateWithCallbackLazy:

import { useStateWithCallbackLazy } from 'use-state-with-callback';

const [count, setCount] = useStateWithCallbackLazy(0);

setCount(count + 1, () => {
   afterSetCountFinished();
});
sytolk avatar Jul 19 '2017 09:07 sytolk

renderSe llamará cada vez que vuelva setStatea renderizar 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 aprovecharlo como último recurso.

Justin Niessner avatar Jan 08 '2016 22:01 Justin Niessner

Hacer setStatede la devolución unPromise

Además de pasar un método callbackto setState(), puedes envolverlo alrededor de una asyncfunción y usar el then()método, lo 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í puedes dar un paso más y crear 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 aún 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 avatar Jun 01 '2018 09:06 Mahdi

Con ganchos en React 16.8 en adelante, es fácil hacer esto conuseEffect

Creé un CodeSandbox para demostrar esto.

useEffect(() => {
  // code to be run when state variables in
  // dependency array changes
}, [stateVariables, thatShould, triggerChange])

Básicamente, useEffectse sincroniza con los cambios de estado y esto se puede usar para renderizar el lienzo.

import React, { useState, useEffect, useRef } from "react";
import { Stage, Shape } from "@createjs/easeljs";
import "./styles.css";

export default function App() {
  const [rows, setRows] = useState(10);
  const [columns, setColumns] = useState(10);
  let stage = useRef()

  useEffect(() => {
    stage.current = new Stage("canvas");
    var rectangles = [];
    var rectangle;
    //Rows
    for (var x = 0; x < rows; x++) {
      // Columns
      for (var y = 0; y < columns; y++) {
        var color = "Green";
        rectangle = new Shape();
        rectangle.graphics.beginFill(color);
        rectangle.graphics.drawRect(0, 0, 32, 44);
        rectangle.x = y * 33;
        rectangle.y = x * 45;

        stage.current.addChild(rectangle);

        var id = rectangle.x + "_" + rectangle.y;
        rectangles[id] = rectangle;
      }
    }
    stage.current.update();
  }, [rows, columns]);

  return (
    <div>
      <div className="canvas-wrapper">
        <canvas id="canvas" width="400" height="300"></canvas>
        <p>Rows: {rows}</p>
        <p>Columns: {columns}</p>
      </div>
      <div className="array-form">
        <form>
          <label>Number of Rows</label>
          <select
            id="numRows"
            value={rows}
            onChange={(e) => setRows(e.target.value)}
          >
            {getOptions()}
          </select>
          <label>Number of Columns</label>
          <select
            id="numCols"
            value={columns}
            onChange={(e) => setColumns(e.target.value)}
          >
            {getOptions()}
          </select>
        </form>
      </div>
    </div>
  );
}

const getOptions = () => {
  const options = [1, 2, 5, 10, 12, 15, 20];
  return (
    <>
      {options.map((option) => (
        <option key={option} value={option}>
          {option}
        </option>
      ))}
    </>
  );
};
sudo bangbang avatar Aug 28 '2020 23:08 sudo bangbang