Puis-je exécuter une fonction après la mise à jour de setState?

175

Je suis très nouveau sur React JS (comme dans, vient de commencer aujourd'hui). Je ne comprends pas très bien comment fonctionne setState. Je combine React et Easel JS pour dessiner une grille basée sur l'entrée utilisateur. Voici mon bin JS: http://jsbin.com/zatula/edit?js,output

Voici le code:

        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")
    );

Vous pouvez voir dans le chutier JS lorsque vous modifiez le nombre de lignes ou de colonnes avec l'un des menus déroulants, rien ne se passera la première fois. La prochaine fois que vous modifiez une valeur de liste déroulante, la grille se dessine sur les valeurs de ligne et de colonne de l'état précédent. Je suppose que cela se produit parce que ma fonction this.drawGrid () s'exécute avant que setState ne soit terminé. Il y a peut-être une autre raison?

Merci pour votre temps et votre aide!

monalisa717
la source

Réponses:

450

setState(updater[, callback]) est une fonction asynchrone:

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

Vous pouvez exécuter une fonction après la fin de setState en utilisant le deuxième paramètre callbackcomme:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});
sytolk
la source
33
ou juste this.setState ({someState: obj}, this.afterSetStateFinished);
Aleksei Prokopov
Je veux ajouter que , dans mon exemple spécifique, je veux probablement appeler this.drawGrid()dans componentDidUpdatecomme @kennedyyu suggéré, parce que tout le temps setStateest terminé, je veux redessiner la grille (donc cela permettrait d' économiser quelques lignes de code), mais faire la même chose .
monalisa717
Excellente réponse ... m'a beaucoup aidé
MoHammaD ReZa DehGhani
Cela me fait gagner beaucoup de temps. Merci.
Nayan Dey le
28

rendersera appelé à chaque fois que vous effectuerez un setStatenouveau rendu du composant en cas de modifications. Si vous déplacez votre appel drawGridlà-bas plutôt que de l'appeler dans vos update*méthodes, vous ne devriez pas avoir de problème.

Si cela ne fonctionne pas pour vous, il y a également une surcharge setStatequi prend un rappel comme deuxième paramètre. Vous devriez pouvoir en profiter en dernier recours.

Justin Niessner
la source
2
Merci - votre première suggestion fonctionne et (surtout) a du sens. J'ai fait ceci: render: function() { this.drawGrid(); return......
monalisa717
3
Euh, s'il vous plaît ne faites pas cela dans render()... la réponse de sytolk devrait être celle acceptée
mfeineis
C'est une mauvaise réponse, setState ira pour la boucle infintie et plantera la page.
Maverick
Justin, je suis intéressé par la raison pour laquelle le rappel de setStatedevrait être utilisé en dernier recours. Je suis d'accord avec la plupart des gens ici pour dire qu'il est logique d'utiliser cette approche.
monalisa717
1
@Rohmer c'est un moyen trop coûteux à exécuter sur chaque appel de rendu alors qu'il n'est évidemment pas nécessaire non plus à chaque appel. Si c'était pur, reactle vdom prendrait soin de ne pas faire trop de travail dans la plupart des cas, c'est l'interopérabilité avec une autre bibliothèque que vous souhaitez minimiser
mfeineis
14

Faire setStaterevenir unPromise

En plus de passer une méthode callbackto setState(), vous pouvez l'envelopper autour d'une asyncfonction et utiliser la then()méthode - qui dans certains cas peut produire un code plus propre:

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

Et ici, vous pouvez prendre cette longueur d'avance et créer une setStatefonction réutilisable qui, à mon avis, est meilleure que la version ci-dessus:

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

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

Cela fonctionne bien dans React 16.4, mais je ne l'ai pas encore testé dans les versions antérieures de React .

Il convient également de mentionner que conserver votre code de rappel dans componentDidUpdatemethod est une meilleure pratique dans la plupart - probablement tous les cas.

Mahdi
la source
11

lorsque de nouveaux accessoires ou états sont reçus (comme vous appelez setStateici), React invoquera certaines fonctions, qui sont appelées componentWillUpdateetcomponentDidUpdate

dans votre cas, ajoutez simplement une componentDidUpdatefonction à appelerthis.drawGrid()

voici le code de travail dans JS Bin

comme je l'ai mentionné, dans le code, componentDidUpdatesera invoqué aprèsthis.setState(...)

alors à l' componentDidUpdateintérieur va appelerthis.drawGrid()

en savoir plus sur le cycle de vie des composants dans React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

Kennedy Yu
la source
au lieu de simplement coller le lien. veuillez ajouter les parties pertinentes dans la réponse.
phoenix le