Por que chamar o método react setState não altera o estado imediatamente?

326

Estou lendo a seção Formulários dedocumentação e apenas tentei esse código para demonstrar o onChangeuso ( JSBIN ).

var React= require('react');

var ControlledForm= React.createClass({
    getInitialState: function() {
        return {
            value: "initial value"
        };
    },

    handleChange: function(event) {
        console.log(this.state.value);
        this.setState({value: event.target.value});
        console.log(this.state.value);

    },

    render: function() {
        return (
            <input type="text" value={this.state.value} onChange={this.handleChange}/>
        );
    }
});

React.render(
    <ControlledForm/>,
  document.getElementById('mount')
);

Quando atualizo o <input/>valor no navegador, o segundo console.logdentro do handleChangeretorno de chamada é impresso da mesma valueforma que o primeiro console.log: Por que não consigo ver o resultado this.setState({value: event.target.value})no escopo do handleChangeretorno de chamada?

tarrsalah
fonte
Se você estiver usando ganchos, dê uma olhada no método set useState que não reflete as alterações imediatamente .
Emile Bergeron

Respostas:

615

Da documentação do React :

setState()não muda imediatamente, this.statemas cria uma transição de estado pendente. O acesso this.stateapós a chamada desse método pode potencialmente retornar o valor existente. Não há garantia de operação síncrona de chamadas setStatee as chamadas podem ser agrupadas para obter ganhos de desempenho.

Se você deseja que uma função seja executada após a alteração do estado, passe-a como um retorno de chamada.

this.setState({value: event.target.value}, function () {
    console.log(this.state.value);
});
Michael Parker
fonte
Boa resposta. A observação que eu preciso fazer é ter cuidado ao usar o valueLink. Funciona bem se você não precisar formatar / mascarar a entrada.
Dherik
42
Você também pode querer conferir componentDidUpdate. Será chamado depois que o estado for alterado.
Keysox 16/05
1
Pergunta rápida, se eu puder, vejo que depois de passarmos a função que precisamos como retorno de chamada para setState, eu esperava que a função fosse executada primeiro antes que render () seja chamado. Mas vejo que a ordem é setState () -> render () -> retorno de chamada de setStates (). isso é normal? E se quisermos controlar nossa renderização com base nas coisas que fazemos no retorno de chamada? shouldComponentUpdate ?
Semuzaboi
2
A alteração do estado de um componente sempre acionará uma nova renderização, a menos que haja um comportamento shouldComponentUpdateque especifique o contrário. O que exatamente você está tentando fazer no retorno de chamada para setStateo qual deseja ocorrer antes da nova renderização?
Michael Parker
4
...porque? Alguém poderia justificar isso?
JackHasaKeyboard
49

Conforme mencionado na documentação do React, não há garantia de setStateser acionado de forma síncrona; portanto, você console.logpode retornar o estado antes da atualização.

Michael Parker menciona passar um retorno de chamada dentro do setState. Outra maneira de lidar com a lógica após a mudança de estado é através do componentDidUpdatemétodo do ciclo de vida, que é o método recomendado nos documentos do React.

Geralmente, recomendamos o uso de componentDidUpdate () para essa lógica.

Isso é particularmente útil quando pode haver setStatedisparos sucessivos e você deseja disparar a mesma função após cada alteração de estado. Em vez de adicionar um retorno de chamada a cada um setState, você pode colocar a função dentro do componentDidUpdate, com lógica específica dentro, se necessário.

// example
componentDidUpdate(prevProps, prevState) {
  if (this.state.value > prevState.value) {
    this.foo();  
  }
}
Yo Wakita
fonte
16

Você pode tentar usar o ES7 assíncrono / aguardar. Por exemplo, usando o seu exemplo:

handleChange: async function(event) {
    console.log(this.state.value);
    await this.setState({value: event.target.value});
    console.log(this.state.value);
}
kurokiiru
fonte
Qual é a sua resposta diferente da outra resposta de alta qualidade?
tod
9
A outra resposta diz respeito ao uso do retorno de chamada em setState (). Pensei em colocar isso aqui para aqueles a quem um caso de uso de retorno de chamada não se aplica. Por exemplo, quando eu mesmo enfrentei esse problema, meu caso de uso envolveu um caso de opção no estado atualizado logo após defini-lo. Portanto, o uso de async / waitit era o preferido de usar um retorno de chamada.
kurokiiru
isso afetará o desempenho se eu sempre usar o wait sempre quando quiser atualizar algum estado e aguardar a atualização? E se eu colocar vários aguardar setStates em uma cadeia um abaixo do outro, ele será renderizado após cada atualização do setState? ou após a última atualização do setState?
user2774480
No que você está perguntando ao usuário2774480, acredito que tudo se resume a um caso de uso específico para determinar qual implementação usar. Se vários setStates forem usados ​​em uma cadeia, o desempenho será afetado e, sim, será renderizado após cada setState, mas me corrija se eu estiver errado.
precisa saber é o seguinte
-1

async-await sintaxe funciona perfeitamente para algo como o seguinte ...

changeStateFunction = () => {
  // Some Worker..

  this.setState((prevState) => ({
  year: funcHandleYear(),
  month: funcHandleMonth()
}));

goNextMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}

goPrevMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}
Ritwik
fonte
-1

Basta colocar - this.setState ({data: value}) é de natureza assíncrona, o que significa que sai da Pilha de Chamadas e volta para a Pilha de Chamadas, a menos que seja resolvido.

Leia sobre o Event Loop para ter uma ideia clara da natureza assíncrona em JS e por que demora para atualizar -

https://medium.com/front-end-weekly/javascript-event-loop-explained-4cd26af121d4

Conseqüentemente -

    this.setState({data:value});
    console.log(this.state.data); // will give undefined or unupdated value

pois leva tempo para atualizar. Para alcançar o processo acima -

    this.setState({data:value},function () {
     console.log(this.state.data);
    });
Vishal Bisht
fonte