Posso executar uma função após a atualização do setState?

174

Eu sou muito novo no React JS (como em, apenas comecei hoje). Não entendo muito bem como o setState funciona. Estou combinando React e Easel JS para desenhar uma grade com base na entrada do usuário. Aqui está o meu bin JS: http://jsbin.com/zatula/edit?js,output

Aqui está o 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")
    );

Você pode ver na lixeira JS quando alterar o número de linhas ou colunas com uma das listas suspensas; nada acontecerá na primeira vez. Na próxima vez que você alterar um valor suspenso, a grade será atraída para os valores de linha e coluna do estado anterior. Suponho que isso esteja acontecendo porque minha função this.drawGrid () está sendo executada antes da conclusão de setState. Talvez haja outro motivo?

Obrigado pelo seu tempo e ajuda!

monalisa717
fonte

Respostas:

449

setState(updater[, callback]) é uma função assíncrona:

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

Você pode executar uma função após o término do setState usando o segundo parâmetro, callbackcomo:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});
sytolk
fonte
32
ou apenas this.setState ({someState: obj}, this.afterSetStateFinished);
Aleksei Prokopov
Quero acrescentar que, no meu exemplo específico, eu provavelmente deseja chamar this.drawGrid()em componentDidUpdatecomo @kennedyyu sugerido, porque qualquer momento setStateestá terminado, vou querer redesenhar a grade (de modo que este iria salvar algumas linhas de código), mas realizar a mesma coisa .
precisa saber é o seguinte
Grande resposta ... me ajudou muito
Mohammad Reza Dehghani
Isso economiza muito tempo para mim. Obrigado.
Nayan Dey 04/02
28

renderserá chamado toda vez que você setStaterenderizar novamente o componente, se houver alterações. Se você mudar sua chamada para drawGridlá, em vez de chamá-la em seus update*métodos, não deverá ter problemas.

Se isso não funcionar para você, também há uma sobrecarga setStateque leva um retorno de chamada como um segundo parâmetro. Você deve tirar proveito disso como último recurso.

Justin Niessner
fonte
2
Obrigado - sua primeira sugestão funciona e (principalmente) faz sentido. Eu fiz isso: render: function() { this.drawGrid(); return......
monalisa717 8/16
3
Ahem, por favor, não faça isso em render()... A resposta de sytolk deve ser o único aceite
mfeineis
Esta é uma resposta errada, setState irá para o loop infintie e travará a página.
Maverick
Justin, eu "estou interessado em saber por que disse que o retorno de setStatedeve ser usado um último recurso Concordo com a maioria das pessoas aqui que não faz sentido usar essa abordagem..
monalisa717
1
@Rohmer, este é um método muito caro de executar em todas as chamadas de renderização, embora obviamente não seja necessário em todas as chamadas. Se fosse puro reacto vdom iria cuidar de não fazer muito trabalho na maioria dos casos, este é de interoperabilidade com outra biblioteca que pretende minimizar
mfeineis
14

Fazendo do setStateretorno umPromise

Além de passar um método callbackpara setState(), você pode envolvê-lo em uma asyncfunção e usar o then()método - que em alguns casos pode produzir um código mais limpo:

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

E aqui você pode dar mais um passo à frente e criar uma setStatefunção reutilizável que, na minha opinião, é melhor que a versão acima:

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

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

Isso funciona bem no React 16.4, mas não o testei nas versões anteriores do React .

Também vale a pena mencionar que manter o código de retorno de chamada no componentDidUpdatemétodo é uma prática recomendada na maioria - provavelmente em todos os casos.

Mahdi
fonte
11

Quando novos adereços ou estados são recebidos (como você chama setStateaqui), o React invoca algumas funções, chamadas componentWillUpdateecomponentDidUpdate

no seu caso, basta adicionar uma componentDidUpdatefunção para chamarthis.drawGrid()

aqui está trabalhando código no JS Bin

como eu mencionei, no código, componentDidUpdateserá invocado apósthis.setState(...)

então por componentDidUpdatedentro vai chamarthis.drawGrid()

leia mais sobre o Ciclo de vida do componente no React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

Kennedy Yu
fonte
em vez de apenas colar o link. adicione as partes relevantes na resposta.
Phoenix