setState ne met pas à jour l'état immédiatement


104

Je voudrais demander pourquoi mon état ne change pas lorsque je fais un événement onclick. J'ai cherché il y a quelque temps que je devais lier la fonction onclick dans le constructeur, mais l'état ne se met toujours pas à jour. Voici mon code:

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

5
La réponse que vous avez acceptée à cette question n'a aucun sens. setStatene retourne pas une promesse. Si cela fonctionnait, cela ne fonctionnait que parce que cela awaitintroduisait un "tick" asynchrone dans la fonction, et il est arrivé que la mise à jour de l'état soit traitée pendant ce tick. Ce n'est pas garanti. Comme le dit cette réponse , vous devez utiliser le rappel d'achèvement (si vous avez vraiment besoin de faire quelque chose après la mise à jour de l'état, ce qui est inhabituel; normalement, vous voulez simplement effectuer un nouveau rendu, ce qui se produit automatiquement).
TJ Crowder

Ce serait bien si vous refusiez d'accepter la réponse actuellement acceptée ou d'accepter une réponse correcte, car elle pourrait alors être utilisée comme doublon pour de nombreuses autres questions. Avoir une mauvaise réponse en haut est trompeur.
Guy Incognito

Réponses:


12

Ce rappel est vraiment compliqué. Utilisez simplement async await à la place:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}

24
Ça n'a aucun sens. React's setStatene rend pas une promesse.
TJ Crowder

16
@TJCrowder a raison. setStatene renvoie pas de promesse, donc il ne faut PAS l'attendre. Cela dit, je pense que je peux voir pourquoi cela fonctionne pour certaines personnes, car await met le fonctionnement interne de setState sur la pile d'appels avant le reste de la fonction, donc il est traité en premier, et semble donc que l'état a été ensemble. Si setState avait ou implémentait de nouveaux appels asynchrones, cette réponse échouerait.Pour implémenter cette fonction correctement, vous pouvez utiliser ceci:await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve()))
Mike Richards

Comment diable a async this.setState({})résolu mon problème? Nous avions du code fonctionnel, des développements supplémentaires ont été effectués mais rien n'a changé cette partie de l'application. Seul l'un des deux objets de setState a été mis à jour, ce qui rend la fonctionnalité renvoyée de manière asynchrone et maintenant les deux objets sont correctement mis à jour. Je n'ai aucune idée de ce qui n'allait pas.
Ondřej Ševčík le

145

Votre état a besoin d'un certain temps pour muter, et comme console.log(this.state.boardAddModalShow)s'exécute avant que l'état mute, vous obtenez la valeur précédente en sortie. Vous devez donc écrire la console dans le rappel de la setStatefonction

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

setStateest asynchrone. Cela signifie que vous ne pouvez pas l'appeler sur une ligne et supposer que l'état a changé sur la suivante.

Selon les documents React

setState()ne mute pas immédiatement this.statemais crée une transition d'état en attente. L'accès this.stateaprès avoir appelé cette méthode peut potentiellement renvoyer la valeur existante. Il n'y a aucune garantie de fonctionnement synchrone des appels à setState et les appels peuvent être groupés pour des gains de performances.

Pourquoi feraient-ils setStateasynchrone

Ceci est dû au fait setState modifie l'état et provoque un nouveau rendu. Cela peut être une opération coûteuse et la rendre synchrone peut laisser le navigateur sans réponse.

Ainsi, les setStateappels sont asynchrones et groupés pour une meilleure expérience et des performances de l'interface utilisateur.


Jusqu'à présent, c'est une excellente idée, MAIS que se passe-t-il si nous ne pouvons pas utiliser console.log parce que vous utilisez des règles eslint?
maudev

2
@maudev, les règles eslint vous empêchent d'avoir console.logs afin qu'ils ne finissent pas en production, mais l'exemple ci-dessus est purement pour le débogage. et console.log et être remplacé par une action qui prend en compte l'état mis à jour
Shubham Khatri

1
Et si le rappel ne fonctionne pas comme vous l'avez dit? le mien non!
Alex Jolig

33

Prend heureusement setState() un rappel. Et c'est là que nous obtenons l'état mis à jour.

Prenons cet exemple.

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

Ainsi, lorsque le rappel se déclenche, this.state est l'état mis à jour.
Vous pouvez obtenir des mutated/updateddonnées en rappel.


1
Vous pouvez également utiliser ce rappel pour passer n'importe quelle fonction initMap(), par exemple
Dende

Marche à suivre! Enfin résolu mon problème. Merci beaucoup.
Ahmet Halilović

11

Comme setSatate est une fonction asynchrone, vous devez donc consolider l'état comme un rappel comme celui-ci.

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

6

setState()ne met pas toujours immédiatement à jour le composant. Il peut grouper ou reporter la mise à jour à plus tard. Cela rend la lecture de this.state juste après avoir appelé setState()un piège potentiel. À la place, utilisez componentDidUpdateou un setStatecallback ( setState(updater, callback)), dont l'un ou l'autre est garanti de se déclencher une fois la mise à jour appliquée. Si vous devez définir l'état en fonction de l'état précédent, lisez l'argument du programme de mise à jour ci-dessous.

setState()mènera toujours à un nouveau rendu à moins qu'il ne shouldComponentUpdate()renvoie false. Si des objets mutables sont utilisés et que la logique de rendu conditionnel ne peut pas être implémentée dans shouldComponentUpdate(), un appel setState()uniquement lorsque le nouvel état diffère de l'état précédent évitera les rendus inutiles.

Le premier argument est une fonction de mise à jour avec la signature:

(state, props) => stateChange

stateest une référence à l'état du composant au moment où la modification est appliquée. Il ne doit pas être directement muté. Au lieu de cela, les changements doivent être représentés en construisant un nouvel objet basé sur l'entrée de l'état et des accessoires. Par exemple, supposons que nous voulions incrémenter une valeur dans state par props.step:

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

2

Si vous souhaitez suivre l'état en cours de mise à jour ou non, une autre façon de faire la même chose est

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

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

De cette façon, vous pouvez appeler la méthode "_stateUpdated" chaque fois que vous essayez de mettre à jour l'état pour le débogage.


1

setState()est asynchrone. La meilleure façon de vérifier si l'état est en cours de mise à jour serait de componentDidUpdate()ne pas mettre un console.log(this.state.boardAddModalShow)après this.setState({ boardAddModalShow: true }).

selon React Docs

Considérez setState () comme une requête plutôt qu'une commande immédiate pour mettre à jour le composant. Pour une meilleure perception des performances, React peut le retarder, puis mettre à jour plusieurs composants en un seul passage. React ne garantit pas que les changements d'état sont appliqués immédiatement


1

Selon React Docs

React ne garantit pas que les changements d'état sont appliqués immédiatement. Cela rend la lecture de this.state juste après avoir appelé setState () un potentiel pitfallet peut potentiellement renvoyer la existingvaleur en raison de la asyncnature. À la place, utilisez componentDidUpdateou un setStaterappel qui est exécuté juste après la réussite de l'opération setState. En général, nous vous recommandons d'utiliser componentDidUpdate()pour une telle logique à la place.

Exemple:

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

0

Pour quiconque essaie de faire cela avec des crochets, vous en avez besoin 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>
  </>
}

Production:

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

-1

lorsque j'exécutais le code et que je vérifiais ma sortie sur la console, cela montrait qu'il n'était pas défini. Après avoir cherché et trouvé quelque chose qui a fonctionné pour moi.

componentDidUpdate(){}

J'ai ajouté cette méthode dans mon code après constructor (). Découvrez le cycle de vie du workflow natif de réaction.

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


-2
 this.setState({
    isMonthFee: !this.state.isMonthFee,
  }, () => {
    console.log(this.state.isMonthFee);
  })

2
C'est exactement ce que la réponse la plus votée explique, mais sans aucune explication et 3 ans de retard. Veuillez ne pas publier de réponses en double, sauf si vous avez quelque chose de pertinent à y ajouter.
Emile Bergeron

-2

Oui car setState est une fonction asynchrone. La meilleure façon de définir l'état juste après avoir écrit l'état d'ensemble est d'utiliser Object.assign comme ceci: Par exemple, vous voulez définir une propriété isValid sur true, faites-le comme ceci


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


Vous pouvez accéder à l'état mis à jour juste après avoir écrit cette ligne.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.