setState não atualiza o estado imediatamente

103

Gostaria de perguntar por que meu estado não está mudando quando faço um evento onclick. Eu pesquisei há um tempo que preciso vincular a função onclick no construtor, mas ainda assim o estado não está atualizando. Este é meu código:

import React from 'react';

import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';

import BoardAddModal from 'components/board/BoardAddModal.jsx';

import style from 'styles/boarditem.css';

class BoardAdd extends React.Component {

    constructor(props){
        super(props);

        this.state = {
            boardAddModalShow: false
        }

        this.openAddBoardModal = this.openAddBoardModal.bind(this);
    }
    openAddBoardModal(){
        this.setState({ boardAddModalShow: true });
// After setting a new state it still return a false value
        console.log(this.state.boardAddModalShow);

    }

    render() {

        return (
            <Col lg={3}>
                <a href="javascript:;" className={style.boardItemAdd} onClick={this.openAddBoardModal}>
                    <div className={[style.boardItemContainer,style.boardItemGray].join(' ')}>
                        Create New Board
                    </div>
                </a>



            </Col>
        )
    }
}

export default BoardAdd
Sydney Loteria
fonte
5
A resposta que você aceitou para esta pergunta não faz sentido. setStatenão retorna uma promessa. Se funcionou, só funcionou porque awaitintroduz um "tique" assíncrono na função e aconteceu que a atualização de estado foi processada durante esse tique. Não é garantido. Como esta resposta diz, você precisa usar o retorno de chamada de conclusão (se você realmente precisar fazer algo depois que o estado for atualizado, o que é incomum; normalmente, você só quer renderizar novamente, o que acontece automaticamente).
TJ Crowder
Seria bom se você não aceitasse a resposta aceita atualmente ou aceitasse uma correta, porque isso poderia ser usado como uma duplicata para muitas outras perguntas. Ter uma resposta incorreta na parte superior é enganoso.
Guy Incognito

Respostas:

12

Este retorno de chamada é realmente confuso. Em vez disso, use async await:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}
Spock
fonte
24
Isso não faz sentido. React setStatenão retorna uma promessa.
TJ Crowder
16
@TJCrowder está certo. setStatenão retorna uma promessa, então NÃO deve ser aguardada. Dito isso, acho que posso ver por que isso está funcionando para algumas pessoas, porque await coloca o funcionamento interno de setState na pilha de chamadas à frente do resto da função, então ele é processado primeiro e, portanto, parece que o estado foi conjunto. Se setState tivesse ou implementasse qualquer nova chamada assíncrona, esta resposta falharia. Para implementar esta função corretamente, você pode usar isto:await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve()))
Mike Richards
Como diabos async this.setState({})resolveu meu problema? Tínhamos o código funcionando, mais algum desenvolvimento foi feito, mas nada mudou essa parte do aplicativo. Apenas um dos dois objetos em setState estava sendo atualizado, tornando a funcionalidade retornada assíncrona e agora ambos os objetos estão sendo atualizados corretamente. Não tenho a mínima ideia do que diabos estava errado.
Ondřej Ševčík
145

Seu estado precisa de algum tempo para sofrer mutação e, como é console.log(this.state.boardAddModalShow)executado antes da mutação do estado, você obtém o valor anterior como saída. Então você precisa escrever o console no callback para a setStatefunção

openAddBoardModal() {
  this.setState({ boardAddModalShow: true }, function () {
    console.log(this.state.boardAddModalShow);
  });
}

setStateé assíncrono. Isso significa que você não pode chamá-lo em uma linha e assumir que o estado mudou na próxima.

De acordo com a documentação do React

setState()não muda imediatamente, this.statemas cria uma transição de estado pendente. Acessar this.statedepois de chamar esse método pode retornar o valor existente. Não há garantia de operação síncrona de chamadas para setState e as chamadas podem ser agrupadas para ganhos de desempenho.

Por que eles fariam setStateassíncronos

Isso ocorre porque setStatealtera o estado e causa uma nova renderização. Esta pode ser uma operação cara e torná-la síncrona pode deixar o navegador sem resposta.

Portanto, as setStatechamadas são assíncronas e em lote para melhor experiência e desempenho da IU.

Shubham Khatri
fonte
Até agora é uma ótima ideia, MAS e se não pudermos usar o console.log porque você está usando regras eslint?
maudev
2
@maudev, as regras eslint impedem que você tenha console.logs para que eles não acabem em produção, mas o exemplo acima é puramente para depuração. e console.log e ser substituído por uma ação que leva em consideração o estado atualizado
Shubham Khatri
1
E se o retorno de chamada não funcionar como você disse? o meu não!
Alex Jolig
33

Felizmente setState() leva um retorno de chamada. E é aqui que obtemos o estado atualizado.

Considere este exemplo.

this.setState({ name: "myname" }, () => {                              
        //callback
        console.log(this.state.name) // myname
      });

Portanto, quando o retorno de chamada é acionado, this.state é o estado atualizado.
Você pode obter mutated/updateddados no retorno de chamada.

Mustkeem K
fonte
1
Você também pode usar este retorno de chamada para passar qualquer função initMap(), por exemplo
Dende
Muito bem! Finalmente resolvi meu problema. Muito obrigado.
Ahmet Halilović
11

Como setSatate é uma função assíncrona, você precisa consolar o estado como um retorno de chamada como este.

openAddBoardModal(){
    this.setState({ boardAddModalShow: true }, () => {
        console.log(this.state.boardAddModalShow)
    });
}
Adnan Shah
fonte
6

setState()nem sempre atualiza imediatamente o componente. Ele pode agrupar ou adiar a atualização para mais tarde. Isso torna a leitura de this.state logo após chamar setState()uma armadilha potencial. Em vez disso, use componentDidUpdateou um setStateretorno de chamada (setState(updater, callback) ), sendo que qualquer um deles será acionado após a aplicação da atualização. Se você precisar definir o estado com base no estado anterior, leia sobre o argumento do atualizador abaixo.

setState()sempre levará a uma nova renderização, a menos que shouldComponentUpdate()retorne falso. Se objetos mutáveis ​​estão sendo usados ​​e a lógica de renderização condicional não pode ser implementada em shouldComponentUpdate(), chamarsetState() apenas quando o novo estado for diferente do estado anterior evitará renderizações desnecessárias.

O primeiro argumento é uma função atualizadora com a assinatura:

(state, props) => stateChange

stateé uma referência ao estado do componente no momento em que a alteração está sendo aplicada. Não deve sofrer mutação direta. Em vez disso, as mudanças devem ser representadas pela construção de um novo objeto com base na entrada de estado e adereços. Por exemplo, suponha que desejamos incrementar um valor no estado por props.step:

this.setState((state, props) => {
    return {counter: state.counter + props.step};
});
Aman Seth
fonte
2

Se você deseja acompanhar se o estado está sendo atualizado ou não, a outra maneira de fazer a mesma coisa é

_stateUpdated(){
  console.log(this.state. boardAddModalShow);
}

openAddBoardModal(){
  this.setState(
    {boardAddModalShow: true}, 
    this._stateUpdated.bind(this)
  );
}

Dessa forma, você pode chamar o método "_stateUpdated" toda vez que tentar atualizar o estado para depuração.

Ravin Gupta
fonte
1

setState()é assíncrono. A melhor forma de verificar se o estado está atualizando seria no componentDidUpdate()e não colocar um console.log(this.state.boardAddModalShow)depois this.setState({ boardAddModalShow: true }).

de acordo com React Docs

Pense em setState () como uma solicitação em vez de um comando imediato para atualizar o componente. Para melhor desempenho percebido, o React pode atrasá-lo e, em seguida, atualizar vários componentes em uma única passagem. React não garante que as mudanças de estado sejam aplicadas imediatamente

Stuli
fonte
1

De acordo com o React Docs

O React não garante que as mudanças de estado sejam aplicadas imediatamente. Isso torna a leitura de this.state logo após chamar setState () um potencial pitfalle pode retornar o existingvalor devido à asyncnatureza. Em vez disso, use componentDidUpdateou um setStateretorno de chamada executado logo após a operação setState ser bem-sucedida. Geralmente, recomendamos usar componentDidUpdate()para essa lógica.

Exemplo:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      counter: 1
    };
  }
  componentDidUpdate() {
    console.log("componentDidUpdate fired");
    console.log("STATE", this.state);
  }

  updateState = () => {
    this.setState(
      (state, props) => {
        return { counter: state.counter + 1 };
      });
  };
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <button onClick={this.updateState}>Update State</button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
khizer
fonte
0

Para quem está tentando fazer isso com ganchos, você precisa useEffect.

function App() {
  const [x, setX] = useState(5)
  const [y, setY] = useState(15) 

  console.log("Element is rendered:", x, y)

  // setting y does not trigger the effect
  // the second argument is an array of dependencies
  useEffect(() => console.log("re-render because x changed:", x), [x])

  function handleXClick() {
    console.log("x before setting:", x)
    setX(10)
    console.log("x in *line* after setting:", x)
  }

  return <>
    <div> x is {x}. </div>
    <button onClick={handleXClick}> set x to 10</button>
    <div> y is {y}. </div>
    <button onClick={() => setY(20)}> set y to 20</button>
  </>
}

Resultado:

Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20
Luke Miles
fonte
-1

quando estava executando o código e verificando minha saída no console, ele mostrava que ele está indefinido. Depois de pesquisar e encontrar algo que funcionou para mim.

componentDidUpdate(){}

Eu adicionei esse método em meu código após constructor (). verifique o ciclo de vida do fluxo de trabalho nativo reactivo.

https://images.app.goo.gl/BVRAi4ea2P4LchqJ8

Pravin Ghorle
fonte
-2
 this.setState({
    isMonthFee: !this.state.isMonthFee,
  }, () => {
    console.log(this.state.isMonthFee);
  })
Atchutha rama reddy Karri
fonte
2
Isso é exatamente o que a resposta mais votada explica, mas sem qualquer explicação e com 3 anos de atraso. Por favor, não poste respostas duplicadas, a menos que você tenha algo relevante a acrescentar.
Emile Bergeron
-2

Sim porque setState é uma função assíncrona. A melhor maneira de definir o estado logo depois de escrever o estado definido é usando Object.assign assim: Por exemplo, você deseja definir uma propriedade isValid como true, faça assim


Object.assign (this.state, {isValid: true})


Você pode acessar o estado atualizado logo após escrever esta linha.

Dipanshu Sharma
fonte