Je veux enregistrer l'état dans localStorage
lorsqu'un composant est démonté. Cela fonctionnait auparavant dans componentWillUnmount
.
J'ai essayé de faire la même chose avec le crochet useEffect
, mais il semble que l'état ne soit pas correct dans la fonction de retour de useEffect
.
Pourquoi donc? Comment puis-je enregistrer l'état sans utiliser de classe?
Voici un exemple factice. Lorsque vous appuyez sur Fermer, le résultat est toujours 0.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function Example() {
const [tab, setTab] = useState(0);
return (
<div>
{tab === 0 && <Content onClose={() => setTab(1)} />}
{tab === 1 && <div>Why is count in console always 0 ?</div>}
</div>
);
}
function Content(props) {
const [count, setCount] = useState(0);
useEffect(() => {
// TODO: Load state from localStorage on mount
return () => {
console.log("count:", count);
};
}, []);
return (
<div>
<p>Day: {count}</p>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => props.onClose()}>close</button>
</div>
);
}
ReactDOM.render(<Example />, document.querySelector("#app"));
J'ai essayé de faire la même chose avec le crochet useEffect, mais il semble que l'état ne soit pas correct dans la fonction de retour de useEffect.
La raison en est due aux fermetures. Une fermeture est une référence d'une fonction aux variables de son champ d'application. Votre callback useEffect
n'est exécuté qu'une seule fois lorsque le composant est monté et donc le callback de retour fait référence à la valeur de comptage initiale de 0.
Les réponses données ici sont ce que je recommanderais. Je recommanderais la réponse de @Jed Richard de passer [count]
à useEffect
, ce qui a pour effet d'écrire dans localStorage
uniquement lorsque le nombre change. C'est mieux que l'approche de ne rien passer du tout d'écriture à chaque mise à jour. À moins que vous ne changiez le nombre extrêmement fréquemment (toutes les quelques ms), vous ne verrez pas de problème de performances et il est correct d'écrire dans localStorage
chaque fois que count
change.
useEffect(() => { ... }, [count]);
Si vous insistez pour n'écrire sur localStorage
que lors du démontage, il y a un hack/solution laid que vous pouvez utiliser - refs. Fondamentalement, vous créez une variable qui est présente tout au long du cycle de vie du composant et que vous pouvez référencer de n'importe où à l'intérieur. Cependant, vous devrez synchroniser manuellement votre état avec cette valeur et c'est extrêmement gênant. Les références ne vous donnent pas le problème de fermeture mentionné ci-dessus car refs est un objet avec un champ current
et plusieurs appels à useRef
vous renverront le même objet. Tant que vous mutez le .current
value, votre useEffect
peut toujours (uniquement) lire la valeur la plus récente.
const {useState, useEffect, useRef} = React;
function Example() {
const [tab, setTab] = useState(0);
return (
<div>
{tab === 0 && <Content onClose={() => setTab(1)} />}
{tab === 1 && <div>Count in console is not always 0</div>}
</div>
);
}
function Content(props) {
const value = useRef(0);
const [count, setCount] = useState(value.current);
useEffect(() => {
return () => {
console.log('count:', value.current);
};
}, []);
return (
<div>
<p>Day: {count}</p>
<button
onClick={() => {
value.current -= 1;
setCount(value.current);
}}
>
-1
</button>
<button
onClick={() => {
value.current += 1;
setCount(value.current);
}}
>
+1
</button>
<button onClick={() => props.onClose()}>close</button>
</div>
);
}
ReactDOM.render(<Example />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
Votre fonction de rappel useEffect affiche le nombre initial, car votre useEffect n'est exécuté qu'une seule fois sur le rendu initial et le rappel est stocké avec la valeur de count qui était présente lors du rendu initial, qui est zéro.
Ce que vous feriez à la place dans votre cas, c'est
useEffect(() => {
// TODO: Load state from localStorage on mount
return () => {
console.log("count:", count);
};
});
Dans les documents React, vous trouverez une raison pour laquelle il est défini comme ceci
Quand exactement React nettoie-t-il un effet? React effectue le nettoyage quand le composant se démonte. Cependant, comme nous l'avons appris précédemment, les effets s'exécutent pour chaque rendu et non pas une seule fois. C'est pourquoi React nettoie également les effets du rendu précédent avant de les exécuter la prochaine fois.
Lisez les documents de réaction sur Why Effects Run on Each Update
Il s'exécute sur chaque rendu, pour l'optimiser, vous pouvez le faire fonctionner sur count
change. Mais il s'agit du comportement actuel proposé de useEffect
, également mentionné dans la documentation, et peut changer dans l'implémentation réelle.
useEffect(() => {
// TODO: Load state from localStorage on mount
return () => {
console.log("count:", count);
};
}, [count]);
L'autre réponse est correcte. Et pourquoi ne pas passer [count]
à votre useEffect, et ainsi enregistrer dans localStorage chaque fois que count
change? Il n'y a pas de véritable pénalité de performance en appelant localStorage comme ça.
Essayez ce modèle:
function Content(props) {
[count, setCount] = useState(0);
// equivalent of componentWillUnmount:
useEffect(() => () => {
console.log('count:', count);
}, []);
// or to have a callback in place every time the state of count changes:
useEffect(() => () => {
console.log('count has changed:', count);
}, [count]);
}
En d'autres termes, N'UTILISEZ PAS const/let/var, mais déclarez votre variable d'état et votre setter à la portée du composant (fonction). Cela évitera leur initialisation incorrecte.
Notez également la fonction légèrement plus supportable (à mon avis!) Qui renvoie une construction de code de fonction pour useEffect.
Au lieu de suivre manuellement vos changements d'état comme dans la réponse acceptée, vous pouvez utiliser useEffect pour mettre à jour la référence.
function Content(props) {
const [count, setCount] = useState(0);
const currentCountRef = useRef(count);
// update the ref if the counter changes
useEffect(() => {
currentCountRef.current = count;
}, [count]);
// use the ref on unmount
useEffect(
() => () => {
console.log("count:", currentCountRef.current);
},
[]
);
return (
<div>
<p>Day: {count}</p>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => props.onClose()}>close</button>
</div>
);
}