useState
useState
est un Hook React qui vous permet d’ajouter une variable d’état dans votre composant.
const [state, setState] = useState(initialState);
- Référence
- Utilisation
- Dépannage
- J’ai mis à jour l’état, mais je vois toujours l’ancienne valeur
- J’ai mis à jour l’état, mais l’affichage ne se met pas à jour
- J’ai une erreur : “Too many re-renders”
- Ma fonction d’initialisation ou ma fonction de mise à jour est exécutée deux fois
- J’essaie de placer une fonction dans un état, mais elle est appelée directement
Référence
useState(initialState)
Appelez useState
à la racine de votre composant pour déclarer une variable d’état.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Clara');
const [todos, setTodos] = useState(() => createTodos());
// ...
La convention est de nommer les variables d’états de cette manière : [something, setSomething]
, en utilisant la déstructuration positionnelle.
Voir d’autres exemples ci-dessous.
Paramètres
initialState
: La valeur initiale de votre état. Ça peut être une valeur de n’importe quel type, mais il existe un comportement spécial pour les fonctions. Cet argument est ignoré après le rendu initial.- Si vous passez une fonction comme
initialState
, elle sera traitée comme une fonction d’initialisation. Elle doit être pure, ne doit pas prendre d’argument, et doit retourner une valeur qui peut être de n’importe quel type. React appellera votre fonction d’initialisation en initialisant le composant, et stockera sa valeur de retour dans votre état initial. Voir un exemple ci-dessous.
- Si vous passez une fonction comme
Valeur renvoyée
useState
retourne un tableau avec exactement deux valeurs :
- L’état courant. Lors du premier rendu, ce sera l’
initialState
que vous avez passé en argument. - La fonction de mise à jour. Elle vous permet de mettre à jour l’état avec une valeur différente et de déclencher un nouveau rendu.
Limitations
useState
est un Hook, vous ne pouvez donc l’appeler qu’à la racine de votre composant ou de vos propres Hooks. Vous ne pouvez pas l’appeler à l’intérieur de boucles ou de conditions. Si nécessaire, extrayez un nouveau composant et déplacez l’état dans celui-ci.- En mode strict, React appellera votre fonction d’initialisation deux fois afin de vous aider à détecter des impuretés accidentelles. Ce comportement est uniquement présent en mode développement et n’affecte pas la production. Si votre fonction d’initialisation est pure (ce qui devrait être le cas), ça ne devrait pas affecter le comportement. Le résultat d’un des appels sera ignoré.
Les fonctions de mise à jour, comme setSomething(nextState)
La fonction de mise à jour renvoyée par useState
permet de mettre à jour l’état avec une valeur différente et de déclencher un nouveau rendu. Vous pouvez passer le prochain état directement, ou passer une fonction qui le calcule sur base de l’état précédent :
const [name, setName] = useState('Edward');
function handleClick() {
setName('Clara');
setAge(a => a + 1);
// ...
Paramètres
nextState
: La valeur désirée de l’état. Elle peut être de n’importe quel type, mais les fonctions reçoivent un traitement spécifique.- Si vous passez une fonction en tant que
nextState
, elle sera traitée comme une fonction de mise à jour. Elle doit être pure, doit prendre l’état en attente comme unique argument, et doit retourner le prochain état. React placera votre fonction de mise à jour dans une file d’attente et fera un nouveau rendu de votre composant. Pendant ce prochain rendu, React calculera le prochain état en appliquant toutes les fonctions de mise à jour l’une après l’autre, en commençant avec l’état précédent. Voir un exemple ci-dessous.
- Si vous passez une fonction en tant que
Valeur renvoyée
Les fonctions de mise à jour (celles renvoyées par useState
) n’ont pas de valeur de retour.
Limitations
-
La fonction de mise à jour ne met à jour que les variables d’état pour le prochain rendu. Si vous lisez la variable d’état après avoir appelé la fonction de mise à jour, vous obtiendrez la même ancienne valeur qui était sur votre écran avant l’appel.
-
Si la nouvelle valeur que vous donnez est identique au
state
actuel, en comparant au moyen deObject.is
, React ne fera pas un nouveau rendu de ce composant et de ses enfants. Il s’agit d’une optimisation. Même si, dans certains cas, React a tout de même besoin d’appeler votre composant sans faire de rendu de ses enfants, ça ne devrait pas affecter votre code. -
React met à jour les états par lots. Il met à jour l’écran après que tous les gestionnaires d’évènements ont été lancés et qu’ils auront appelé leurs fonctions de mise à jour. Ça évite des rendus inutiles suite à un unique évènement. Dans les rares cas où vous auriez besoin de forcer React à mettre à jour l’écran plus tôt, par exemple pour accéder au DOM, vous pouvez utiliser
flushSync
. -
Il est possible d’appeler la fonction de mise à jour pendant le rendu, mais uniquement au sein du composant en cours de rendu. React ignorera le JSX résultat pour refaire immédiatement un rendu avec le nouvel état. Cette approche est rarement nécessaire, mais vous pouvez l’utiliser pour stocker des informations des précédents rendus. Voir un exemple ci-dessous.
-
En mode strict, React appellera votre fonction d’initialisation deux fois afin de vous aider à détecter des impuretés accidentelles. Ce comportement est spécifique au mode développement et n’affecte pas la production. Si votre fonction de mise à jour est pure (ce qui devrait être le cas), ça ne devrait pas affecter le comportement. Le résultat d’un des appels sera ignoré.
Utilisation
Ajouter un état à un composant
Appelez useState
à la racine de votre composant pour déclarer une ou plusieurs variables d’état.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Clara');
// ...
Par convention, on nomme les variables d’état comme suit : [something, setSomething]
, en utilisant la déstructuration positionnelle.
useState
renvoie un tableau avec exactement deux valeurs :
- L’état courant de cette variable d’état, initialement le même que l’état initial que vous avez passé en entrée.
- La fonction de mise à jour qui vous permet d’en modifier la valeur lors d’une interaction.
Pour mettre à jour l’affichage, appelez la fonction de mise à jour avec le prochain état :
function handleClick() {
setName('Robin');
}
React stockera ce prochain état, fera un nouveau rendu de votre composant avec les nouvelles valeurs, et mettra à jour l’interface utilisateur (UI pour User Interface, NdT).
Exemple 1 sur 4 · Compteur (nombre)
Dans cet exemple, la variable d’état count
contient un nombre. Elle est incrémentée en cliquant sur un bouton.
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> Vous avez cliqué sur ce bouton {count} fois </button> ); }
Mettre à jour l’état sur base de l’état précédent
Supposons que age
vaille 42
. Ce gestionnaire appelle setAge(age + 1)
trois fois :
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
Cependant, après un click, age
ne va valoir que 43
, plutôt que 45
! C’est parce qu’appeler la fonction de mise à jour ne met pas à jour la variable d’état age
dans le code en cours d’exécution. Donc, chaque appel à setAge(age + 1)
devient setAge(43)
.
Pour résoudre ce problème, vous devez passer une fonction de mise à jour à setAge
au lieu du prochain état :
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
Ici, a => a + 1
est votre fonction de mise à jour. Elle prend l’état en attente et calcule à partir de celui-ci le prochain état.
React met vos fonctions de mise à jour dans une file d’attente. Ensuite, pendant le prochain rendu, il va les appeler dans le même ordre :
a => a + 1
recevra un état en attente à42
et renverra43
comme prochain état.a => a + 1
recevra un état en attente à43
et renverra44
comme prochain état.a => a + 1
recevra un état en attente à44
et renverra45
comme prochain état.
Il n’y a pas d’autres mises à jour en file d’attente, React stockera donc au final 45
comme état courant.
La convention veut qu’on nomme l’argument de l’état en attente selon la première lettre du nom de la variable d’état, comme a
pour age
. Cependant, vous pouvez tout aussi bien le nommer prevAge
, ou quelque chose d’autre que vous trouveriez plus explicite.
En développement, React pourra appeler vos mises à jour deux fois pour vérifier qu’elles sont pures.
En détail
Certains vous recommandront peut-être de toujours écrire votre code de cette manière, si l’état que vous mettez à jour est calculé depuis l’état précédent : setAge(a => a + 1)
. Il n’y a aucun mal à ça, mais ce n’est pas toujours nécessaire.
Dans la plupart des cas, il n’y a aucune différence entre ces deux approches. React s’assurera toujours, pour les actions intentionnelles des utilisateurs, que l’état age
sera à jour pour le prochain click. Il n’y a donc aucun risque qu’un gestionnaire de clic voie un age
“obsolète” au début d’un écouteur d’évènement.
Cependant, si vous effectuez plusieurs mises à jour pour le même évènement, les fonctions de mises à jours peuvent être utiles. Elles sont également utiles s’il n’est pas pratique d’accéder à la variable d’état elle-même (vous pourrez rencontrer ce cas lorsque vous cherchez à optimiser les rendus).
Si vous souhaitez rester cohérent·e dans le style employé, au prix d’une syntaxe légèrement plus verbeuse, vous pouvez choisir de toujours recourir à une fonction de mise à jour lorsque l’état que vous mettez à jour est calculé à partir de l’état précédent. S’il est calculé depuis l’état précédent d’une autre variable d’état, vous pourrez peut-être les combiner en un seul objet et utiliser un réducteur.
Exemple 1 sur 2 · Passer la fonction de mise à jour
Cet exemple passe la fonction de mise à jour, du coup le bouton “+3” fonctionne.
import { useState } from 'react'; export default function Counter() { const [age, setAge] = useState(42); function increment() { setAge(a => a + 1); } return ( <> <h1>Votre âge : {age}</h1> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <button onClick={() => { increment(); }}>+1</button> </> ); }
Mettre à jour des objets et des tableaux dans un état
Vous pouvez utiliser des objets et des tableaux dans un état. En React, un état est considéré comme en lecture seule, vous devez donc remplacer vos objets existants plutôt que les modifier. Par exemple, si vous avez un objet form
dans un état, ne le modifiez pas :
// 🚩 Ne modifiez pas un objet dans un état comme ceci :
form.firstName = 'Clara';
À la place, replacez l’objet entier en en créant un nouveau :
// ✅ Remplacez l'état avec un nouvel objet
setForm({
...form,
firstName: 'Clara'
});
Consultez Mettre à jour les objets d’un état et Mettre à jour les tableaux d’un état pour en savoir plus.
Exemple 1 sur 4 · Formulaire (objet)
Dans cet exemple, la variable d’état form
contient un objet. Chaque champ de saisie possède un gestionnaire de changement qui appelle setForm
avec le prochain état du formulaire tout entier. La syntaxe de spread {...form}
permet de s’assurer que l’état de l’objet est remplacé, plutôt que modifié.
import { useState } from 'react'; export default function Form() { const [form, setForm] = useState({ firstName: 'Barbara', lastName: 'Hepworth', email: 'bhepworth@sculpture.com', }); return ( <> <label> Prénom : <input value={form.firstName} onChange={e => { setForm({ ...form, firstName: e.target.value }); }} /> </label> <label> Nom de famille : <input value={form.lastName} onChange={e => { setForm({ ...form, lastName: e.target.value }); }} /> </label> <label> Mail : <input value={form.email} onChange={e => { setForm({ ...form, email: e.target.value }); }} /> </label> <p> {form.firstName}{' '} {form.lastName}{' '} ({form.email}) </p> </> ); }
Éviter de recalculer l’état initial
React sauvegarde l’état initial et l’ignore lors des rendus ultérieurs.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ...
Même si le résultat de createInitialTodos()
est utilisé seulement pour le rendu initial, vous appelez tout de même cette fonction à chaque rendu. Ça peut gâcher les performances si vous créez de grands tableaux ou effectuez des calculs coûteux.
Pour résoudre cette problématique, vous pouvez passer plutôt une fonction d’initialisation à useState
:
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ...
Remarquez que vous passez désormais createInitialTodos
, c’est-à-dire la fonction elle-même, au lieu de createInitialTodos()
, qui est le résultat de l’appel. Si vous passez une fonction à useState
, React ne l’appellera que pendant l’initialisation.
React pourra appeler vos fonctions d’initialisations deux fois afin de vérifier qu’elles sont pures.
Exemple 1 sur 2 · Passer la fonction d’initialisation
Cet exemple passe la fonction createInitialTodos
en tant que fonction d’initialisation, afin qu’elle ne soit lancée que lors de l’initialisation. Elle n’est pas lancée quand le composant effectue un nouveau rendu, par exemple lorsque vous tapez dans le champ de saisie.
import { useState } from 'react'; function createInitialTodos() { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: 'Élément ' + (i + 1) }); } return initialTodos; } export default function TodoList() { const [todos, setTodos] = useState(createInitialTodos); const [text, setText] = useState(''); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); setTodos([{ id: todos.length, text: text }, ...todos]); }}>Ajouter</button> <ul> {todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
Réinitialiser l’état avec une clé
Vous rencontrerez souvent la prop key
dans des rendus de listes. Sachez qu’elle a une autre utilité.
Vous pouvez réinitialiser l’état d’un composant en lui passant une key
différente. Dans cet exemple, le bouton Réinitialiser change la variable d’état version
, laquelle est passée comme key
au Form
. Quand la key
change, React recrée le composant Form
(et tous ses enfants) à partir de zéro, ce qui réinitialise son état.
Consultez Préserver et réinitialiser l’état pour en savoir plus.
import { useState } from 'react'; export default function App() { const [version, setVersion] = useState(0); function handleReset() { setVersion(version + 1); } return ( <> <button onClick={handleReset}>Réinitialiser</button> <Form key={version} /> </> ); } function Form() { const [name, setName] = useState('Clara'); return ( <> <input value={name} onChange={e => setName(e.target.value)} /> <p>Bonjour, {name}.</p> </> ); }
Stocker les informations des rendus précédents
La plupart du temps, vous mettrez à jour les états dans des gestionnaires d’évènements. Cependant, dans de rares cas, vous pourriez vouloir ajuster l’état en fonction du rendu — par exemple, pour modifier une variable d’état quand une propriété change.
Dans la plupart des cas, vous n’en avez en réalité pas besoin :
- Si la valeur dont vous avez besoin peut être totalement calculée à partir des propriétés actuelles ou d’un autre état, supprimez carrément cette variable d’état redondante. Si vous craignez d’effectuer alors de nouveaux calculs trop fréquemment, le Hook
useMemo
peut vous aider. - Si vous voulez réinitialiser l’intégralité des états du composant et de ses enfants, passez une
key
différente à votre composant. - Si vous le pouvez, mettez à jour tous les états pertinents dans des gestionnaires d’évènements.
Dans de rares autres cas, il existe une approche que vous pouvez utiliser pour mettre à jour un état sur la base des valeurs actuelles du rendu : appelez sa fonction de mise à jour pendant le rendu de votre composant.
Voici un exemple. Ce composant CountLabel
affiche une prop count
qui lui est passée :
export default function CountLabel({ count }) {
return <h1>{count}</h1>
}
Mettons que vous vouliez indiquer si le compteur a augmenté ou diminué depuis le dernier changement. La prop count
ne vous permet pas de le savoir — vous avez besoin de garder trace de sa dernière valeur. Ajoutez la variable d’état prevCount
pour y parvenir. Puis ajoutez une autre variable d’état appelée trend
qui permet de savoir si le compteur a augmenté ou diminué. Comparez prevCount
avec count
, et, s’ils ne sont pas égaux, mettez à jour prevCount
et trend
. Vous pouvez maintenant afficher tant la prop du compteur courant que la façon dont elle a changé depuis le dernier rendu.
import { useState } from 'react'; export default function CountLabel({ count }) { const [prevCount, setPrevCount] = useState(count); const [trend, setTrend] = useState(null); if (prevCount !== count) { setPrevCount(count); setTrend(count > prevCount ? 'augmenté' : 'diminué'); } return ( <> <h1>{count}</h1> {trend && <p>Le compteur a {trend}</p>} </> ); }
Notez que si vous appelez une fonction de mise à jour pendant le rendu, elle doit être assujettie à une condition, telle que prevCount !== count
, laquelle contrôle l’appel, tel que setPrevCount(count)
. À défaut, votre composant effectuera des rendus en boucle jusqu’à cause un plantage. De plus, vous ne pouvez mettre ainsi à jour que l’état du composant en cours de rendu. Appeler une fonction de mise à jour issue d’un autre composant pendant le rendu entraîne une erreur. Pour finir, votre appel de mise à jour devrait toujours mettre à jour l’état sans le modifier — appeler depuis le rendu ne vous autorise pas à enfreindre les règles des fonctions pures.
Cette approche peut être délicate à bien comprendre : la plupart du temps, vous devriez l’éviter. Cependant, c’est toujours mieux que de mettre à jour un état au sein d’un effet. Lorsque vous appelez une fonction de mise à jour pendant le rendu, React effectuera un nouveau rendu de ce composant immédiatement après que votre fonction composant a terminé avec un return
, avant d’effectuer le rendu de ses enfants. Grâce à ça, les enfants n’auront pas besoin d’effectuer deux rendus. Le reste de votre fonction composant s’exécutera toujours (et le résultat sera jeté). Si votre condition se situe en dessous de tous les appels à des Hooks, vous pouvez même y ajouter un return;
anticipé pour déclencher le nouveau rendu plus tôt.
Dépannage
J’ai mis à jour l’état, mais je vois toujours l’ancienne valeur
Appeler la fonction de mise à jour ne modifie pas l’état dans le code en cours d”exécution :
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Demande un nouveau rendu avec 1
console.log(count); // Toujours 0 !
setTimeout(() => {
console.log(count); // Encore 0 !
}, 5000);
}
C’est parce que l’état se comporte comme un instantané. Mettre à jour l’état planifie un autre rendu avec la nouvelle valeur d’état, mais n’affecte pas la variable JavaScript count
dans le gestionnaire d’évènements en train de s’exécuter.
Si vous avez besoin du prochain état, vous pouvez le sauvegarder dans une variable avant de le passer dans la fonction de mise à jour :
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
J’ai mis à jour l’état, mais l’affichage ne se met pas à jour
React ignorera votre mise à jour si le prochain état est égal à l’état précédent, en comparant au moyen de Object.is
. Ça arrive généralement lorsque vous modifiez directement un objet ou un tableau dans l’état :
obj.x = 10; // 🚩 Erroné : mutation d’un objet existant
setObj(obj); // 🚩 Ne fait rien
Vous avez modifié un objet obj
existant et vous l’avez passé à setObj
, donc React ignore la mise à jour (c’est la même référence, le même objet en mémoire). Pour corriger ça, vous devez vous assurer de toujours remplacer les objets et les tableaux de l’état plutôt que les modifier :
// ✅ Correct : création d’un nouvel objet
setObj({
...obj,
x: 10
});
J’ai une erreur : “Too many re-renders”
Vous verrez peut-être une erreur disant : Too many re-renders. React limits the number of renders to prevent an infinite loop
(« Trop re rendus successifs. React limite le nombre de rendus pour éviter une boucle infinie », NdT). Ça signifie le plus souvent que vous mettez à jour un état de manière inconditionnelle pendant le rendu, de sorte que votre composant entre dans une boucle : rendu, mise à jour de l’état (qui déclenche un rendu), rendu, mise à jour de l’état (qui entraîne un rendu), etc. Le plus souvent, ça vient d’une erreur classique de fourniture d’un gestionnaire d’évènement :
// 🚩 Erroné : appelle le gestionnaire pendant le rendu
return <button onClick={handleClick()}>Click me</button>
// ✅ Correct : passe le gestionnaire d’évènement
return <button onClick={handleClick}>Click me</button>
// ✅ Correct : passe une fonction créée à la volée
return <button onClick={(e) => handleClick(e)}>Click me</button>
Si vous n’arrivez pas à trouver le cause de cette erreur, cliquez dans la console sur la flèche à côté de l’erreur, et examinez votre pile d’appels JavaScript afin d’y repérer la fonction de mise à jour responsable de l’erreur.
Ma fonction d’initialisation ou ma fonction de mise à jour est exécutée deux fois
En mode strict, React appellera certaines de vos fonctions plutôt deux fois qu’une :
function TodoList() {
// Cette fonction composant sera appelée deux fois par rendu.
const [todos, setTodos] = useState(() => {
// Cette fonction d'initialisation sera appelée deux fois par rendu.
return createTodos();
});
function handleClick() {
setTodos(prevTodos => {
// Cette fonction de mise à jour sera appelée deux fois par clic.
return [...prevTodos, createTodo()];
});
}
// ...
C’est voulu, et ça ne devrait pas casser votre code.
Ce comportement, uniquement présent en développement, vous aide à garder vos composants purs. React utilise le résultat d’un des appels et ignore le résultat de l’autre. Tant que vos composants, vos fonctions d’initialisation, et vos fonctions de mise à jour sont pures, ça ne devrait pas affecter le comportement. Cependant, si elles sont accidentellement impures, ça vous aide à détecter le problème.
Par exemple, cette fonction de mise à jour impure modifie directement un tableau dans un état :
setTodos(prevTodos => {
// 🚩 Erreur : modification en place de l'état
prevTodos.push(createTodo());
});
Comme React a appelé votre fonction de mise à jour à deux reprises, vous verrez que la tâche a été ajoutée deux fois, et vous saurez qu’il y a une erreur. Dans cet exemple, vous pouvez corriger l’erreur en remplaçant le tableau, plutôt que de le modifier :
setTodos(prevTodos => {
// ✅ Correct : remplacement par un nouvel état
return [...prevTodos, createTodo()];
});
Maintenant que cette fonction de mise à jour est pure, l’appeler une fois de plus n’entraîne aucune différence de comportement. C’est en cela que le double appel par React vous aide à détecter les problèmes. Seuls les composants, les fonctions d’initialisation et les fonctions de mise à jour doivent être purs. Les gestionnaires d’évènements n’ont pas besoin d’être purs, aussi React ne les appellera jamais deux fois.
Consultez Garder les composants purs pour en savoir plus.
J’essaie de placer une fonction dans un état, mais elle est appelée directement
Vous ne pouvez pas mettre une fonction dans un état comme ceci :
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
Comme vous passez une fonction, React part du principe que someFunction
est une fonction d’initialisation, et que someOtherFunction
est une fonction de mise à jour ; il va donc les appeler pour stocker leurs résultats. Afin de stocker effectivement une fonction, dans les deux cas vous devrez la préfixer par () =>
. Dans ce cas, React stockera les fonctions que vous passez.
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}