Pourquoi l'appel de la méthode react setState ne mute pas l'état immédiatement?

326

Je lis la section Formulaires dedocumentation et vient d'essayer ce code pour démontrer l' onChangeutilisation ( 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')
);

Lorsque je mets à jour la <input/>valeur dans le navigateur, la seconde console.logà l'intérieur du handleChangerappel imprime la même chose valueque la première console.log, pourquoi je ne vois pas le résultat de this.setState({value: event.target.value})dans la portée du handleChangerappel?

tarrsalah
la source
Si vous utilisez des hooks, jetez un œil à la méthode set useState ne reflétant pas immédiatement le changement .
Emile Bergeron

Réponses:

615

De la documentation de React :

setState()ne mute pas immédiatement this.statemais crée une transition d'état en attente. Accéder 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 vers setStateet les appels peuvent être groupés pour des gains de performances.

Si vous souhaitez qu'une fonction soit exécutée après le changement d'état, transmettez-la en tant que rappel.

this.setState({value: event.target.value}, function () {
    console.log(this.state.value);
});
Michael Parker
la source
Bonne réponse. L'observation que je dois faire est de faire attention à utiliser valueLink. Cela fonctionne bien si vous n'avez pas à formater / masquer l'entrée.
Dherik
42
Vous pourriez également vouloir vérifier componentDidUpdate. Il sera appelé après le changement d'état.
Keysox
1
Question rapide si je peux, je vois une fois que nous avons passé la fonction dont nous avons besoin comme rappel à setState, j'espérais que le func serait exécuté en premier avant que render () soit appelé. Mais je vois que l'ordre est setState () -> render () -> callback () de setStates. est-ce normal? Que se passe-t-il si nous voulons contrôler notre rendu en fonction de ce que nous faisons en rappel? shouldComponentUpdate ?
semuzaboi
2
Changer l'état d'un composant déclenchera toujours un nouveau rendu, sauf si un comportement shouldComponentUpdatespécifie le contraire. Qu'essayez-vous exactement de faire dans le rappel setStateauquel vous passez que vous souhaitez voir se produire avant le rendu?
Michael Parker
4
...Pourquoi? Quelqu'un pourrait-il justifier cela?
JackHasaKeyboard
49

Comme mentionné dans la documentation React, il n'y a aucune garantie d' setStateêtre déclenché de façon synchrone, vous console.logpouvez donc retourner l'état avant sa mise à jour.

Michael Parker mentionne avoir passé un rappel au sein du setState. Une autre façon de gérer la logique après un changement d'état est via la componentDidUpdateméthode du cycle de vie, qui est la méthode recommandée dans les documents React.

Généralement, nous recommandons d'utiliser à la place componentDidUpdate () pour une telle logique.

Ceci est particulièrement utile lorsqu'il peut y avoir des setStatedéclenchements successifs et que vous souhaitez déclencher la même fonction après chaque changement d'état. Plutôt que d'ajouter un rappel à chacun setState, vous pouvez placer la fonction à l'intérieur du componentDidUpdate, avec une logique spécifique à l'intérieur si nécessaire.

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

Vous pouvez essayer d'utiliser ES7 async / wait. Par exemple, en utilisant votre exemple:

handleChange: async function(event) {
    console.log(this.state.value);
    await this.setState({value: event.target.value});
    console.log(this.state.value);
}
kurokiiru
la source
En quoi votre réponse est-elle différente de l'autre réponse de haute qualité?
tod
9
L'autre réponse concerne l'utilisation du rappel dans setState (). Je pensais que je mettais cela ici pour ceux auxquels un cas d'utilisation de rappel ne s'applique pas. Par exemple, lorsque j'ai moi-même rencontré ce problème, mon cas d'utilisation impliquait un cas de commutateur sur l'état mis à jour juste après l'avoir défini. Par conséquent, l'utilisation de async / wait a été préférée à l'utilisation d'un rappel.
kurokiiru
cela affectera-t-il les performances si j'utilise toujours attendre toujours quand je veux mettre à jour un état puis attendre qu'il soit mis à jour? Et si je mets plusieurs setStates en attente les uns au-dessous des autres, le rendu sera-t-il effectué après chaque mise à jour de setState? ou après la dernière mise à jour setState?
user2774480
Sur ce que vous demandez à l'utilisateur 2774480, je pense que tout se résume à un cas d'utilisation spécifique pour déterminer quelle implémentation utiliser. Si plusieurs setStates sont utilisés dans une chaîne, les performances seront affectées, et oui, elles s'afficheront après chaque setState, mais corrigez-moi si je me trompe.
kurokiiru
-1

async-await la syntaxe fonctionne parfaitement pour quelque chose comme ce qui suit ...

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
la source
-1

En termes simples, this.setState ({data: value}) est de nature asynchrone, ce qui signifie qu'il quitte la pile d'appels et ne revient à la pile d'appels que s'il est résolu.

Veuillez lire la boucle d'événements pour avoir une image claire de la nature asynchrone dans JS et pourquoi la mise à jour prend du temps -

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

Par conséquent -

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

car il faut du temps pour mettre à jour. Pour réaliser le processus ci-dessus -

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