¿Puedo ejecutar una función después de que setState haya terminado de actualizarse?
Soy muy nuevo en ReactJS (como recién comenzado hoy). No entiendo muy bien cómo setState
funciona. 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 setState
que se complete. ¿Quizás haya otra razón?
¡Gracias por tu tiempo y ayuda!
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 callback
como:
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();
});
render
Se llamará cada vez que vuelva setState
a renderizar el componente si hay cambios. Si mueve su llamada drawGrid
allí 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 setState
que toma una devolución de llamada como segundo parámetro. Debería poder aprovecharlo como último recurso.
Hacer setState
de la devolución unPromise
Además de pasar un método callback
to setState()
, puedes envolverlo alrededor de una async
funció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 setState
funció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 componentDidUpdate
método es una mejor práctica en la mayoría (probablemente en todos) los casos.
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, useEffect
se 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>
))}
</>
);
};