web-dev-qa-db-fra.com

La méthode set useState ne reflète pas le changement immédiatement

J'essaie d'apprendre les hooks et la méthode useState m'a rendu confus. J'attribue une valeur initiale à un état sous la forme d'un tableau. La méthode set dans useState ne fonctionne pas pour moi même avec spread(...) ou without spread operator. J'ai créé une API sur un autre PC que j'appelle et récupère les données que je veux mettre dans l'état.

Voici mon code:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const StateSelector = () => {
  const initialValue = [
    {
      category: "",
      photo: "",
      description: "",
      id: 0,
      name: "",
      rating: 0
    }
  ];

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {
        //const response = await fetch(
        //`http://192.168.1.164:5000/movies/display`
        //);
        //const json = await response.json();
        //const result = json.data.result;
        const result = [
          {
            category: "cat1",
            description: "desc1",
            id: "1546514491119",
            name: "randomname2",
            photo: null,
            rating: "3"
          },
          {
            category: "cat2",
            description: "desc1",
            id: "1546837819818",
            name: "randomname1",
            rating: "5"
          }
        ];
        console.log(result);
        setMovies(result);
        console.log(movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

  return <p>hello</p>;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);

La fonction setMovies(result) ainsi que setMovies(...result) ne fonctionne pas. Pourrait utiliser de l'aide ici.

Je m'attends à ce que la variable de résultat soit poussée dans le tableau des films.

92
Pranjal

Tout comme les composants setState in Class créés en étendant React.Component Ou React.PureComponent, La mise à jour d'état à l'aide du programme de mise à jour fourni par useState hook est également asynchrone, et ne se reflétera pas et ne se mettra pas à jour immédiatement mais déclenchera un nouveau rendu

setMovies(result);
console.log(movies) // movies here will not be updated

Si vous souhaitez effectuer une action sur la mise à jour de l'état, vous devez utiliser le hook useEffect, un peu comme utiliser componentDidUpdate dans les composants de classe car le setter renvoyé par useState n'a pas de modèle de rappel

useEffect(() => {
    // action on update of movies
}, [movies]);

En ce qui concerne la syntaxe de mise à jour de l'état, setMovies(result) remplacera la précédente valeur de movies dans l'état par celles disponibles à partir de la demande asynchrone

Cependant, si vous souhaitez fusionner la réponse avec les valeurs existantes, vous devez utiliser la syntaxe de rappel de la mise à jour de l'état ainsi que l'utilisation correcte de la syntaxe répartie comme

setMovies(prevMovies => ([...prevMovies, ...result]));
126
Shubham Khatri

Détails supplémentaires sur le réponse précédente :

Bien que React's setState is asynchrone (à la fois les classes et les hooks), et qu'il soit tentant d'utiliser ce fait pour expliquer le comportement observé, ce n'est pas le pourquoi ça arrive.

TLDR: La raison en est une étendue fermeture autour d'une valeur const immuable.


Solutions:

  • lire la valeur dans la fonction de rendu (pas dans les fonctions imbriquées):

    useEffect(() => { setMovies(result) }, [])
    console.log(movies)
    
  • ajoutez la variable dans les dépendances (et utilisez la règle react-hooks/exhaustive-deps eslint):

    useEffect(() => { setMovies(result) }, [])
    useEffect(() => { console.log(movies) }, [movies])
    
  • utilisez une référence mutable (lorsque ce qui précède n'est pas possible):

    const moviesRef = useRef(initialValue)
    useEffect(() => {
      moviesRef.current = result
      console.log(moviesRef.current)
    }, [])
    

Explication pourquoi cela se produit:

Si l'async était la seule raison, il serait possible de await setState().

Cependant, props et state sont tous deux supposés immuables pendant 1 rend .

Traitez this.state Comme s'il était immuable.

Avec les hooks, cette hypothèse est renforcée en utilisant des valeurs constantes avec le mot clé const:

const [state, setState] = useState('initial')

La valeur peut être différente entre 2 rendus, mais reste une constante à l'intérieur du rendu lui-même et dans n'importe quel fermetures (fonctions qui vivent plus longtemps même après le rendu, par exemple useEffect, gestionnaires d'événements, dans toute promesse ou setTimeout).

Envisagez de suivre un faux, mais synchrone , implémentation de type React:

// sync implementation:

let internalState
let renderAgain

const setState = (updateFn) => {
  internalState = updateFn(internalState)
  renderAgain()
}

const useState = (defaultState) => {
  if (!internalState) {
    internalState = defaultState
  }
  return [internalState, setState]
}

const render = (component, node) => {
  const {html, handleClick} = component()
  node.innerHTML = html
  renderAgain = () => render(component, node)
  return handleClick
}

// test:

const MyComponent = () => {
  const [x, setX] = useState(1)
  console.log('in render:', x) // ✅
  
  const handleClick = () => {
    setX(current => current + 1)
    console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
  }
  
  return {
    html: `<button>${x}</button>`,
    handleClick
  }
}

const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>
39
Aprillion