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.
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]));
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.
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)
}, [])
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>