react usestate est la façon la plus élémentaire de stocker des données changeantes (l'état) à l'intérieur d'un composant fonction. Un compteur, un champ de formulaire, l'ouverture ou la fermeture d'un menu déroulant… tout ce qui change suite à une interaction de l'utilisateur est de l'état, et tu dois le conserver avec useState pour que React reflète ce changement à l'écran. Modifier une variable ordinaire ne déclenche pas React ; useState, en revanche, stocke la valeur et re-rend le composant lorsqu'elle change.
Comment fonctionne useState
useState reçoit une valeur initiale et renvoie un tableau de deux éléments : la valeur actuelle et la fonction qui la modifie. On nomme les deux d'un coup grâce à la déstructuration de tableau :
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Cliqué : {count}
</button>
);
}
Ici, count est la valeur actuelle et setCount la fonction qui la modifie. Le 0 de useState(0) n'est utilisé qu'au premier rendu ; aux rendus suivants, React se souvient de la valeur précédente. Le nommage t'appartient entièrement, mais la convention [x, setX] est standard.
Ne jamais muter l'état directement
L'erreur la plus fréquente en React est de muter l'état directement. La seule façon de changer l'état est d'appeler la fonction set. Une affectation directe, comme ci-dessous, ne met pas l'écran à jour :
// FAUX — React ne le remarquera pas
count = count + 1;
// CORRECT — déclenche un nouveau rendu
setCount(count + 1);
La même règle s'applique aux objets et aux tableaux. Au lieu de modifier l'objet existant, tu crées un nouvel objet. La raison : React détecte les changements par comparaison de référence (l'ancienne valeur est-elle identique à la nouvelle) ; si tu mutes la même référence, React ne comprendra pas que quelque chose a changé.
const [user, setUser] = useState({ nom: "Ada", age: 30 });
// FAUX
user.age = 31;
setUser(user);
// CORRECT — un nouvel objet via le spread
setUser({ ...user, age: 31 });
Mises à jour fonctionnelles : pourquoi et quand ?
Au lieu de passer une valeur directement à la fonction set, tu peux aussi passer une fonction qui reçoit l'état précédent en argument. C'est ce qu'on appelle une mise à jour fonctionnelle, et c'est la méthode la plus sûre lorsque la nouvelle valeur dépend de l'ancienne :
// dépend de la valeur précédente : préfère la forme fonctionnelle
setCount(prev => prev + 1);
La différence apparaît quand tu mets l'état à jour plusieurs fois dans le même événement. Compare ces deux exemples :
// Ceci n'incrémente le compteur que de 1
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// Ceci incrémente le compteur de 3
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
Dans le premier bloc, count est la valeur figée de ce rendu (par exemple 0), donc les trois appels calculent 0 + 1. Sous la forme fonctionnelle, chaque appel reçoit le résultat de la mise à jour précédente, et le résultat s'accumule donc correctement.
Le batching (regroupement) des mises à jour
React regroupe plusieurs mises à jour d'état au sein du même gestionnaire d'événement en un seul rendu ; c'est le batching. Ainsi, même si tu fais trois appels set distincts, le composant se re-rend une fois et non trois. Cela améliore les performances et évite que des états intermédiaires inachevés apparaissent à l'écran.
function handleClick() {
setOuvert(true);
setCompteur(c => c + 1);
setMessage("mis à jour");
// les trois sont appliqués en un seul rendu
}
Avec React 18, ce comportement s'applique non seulement aux gestionnaires d'événements mais aussi à setTimeout, aux Promesses et aux autres callbacks asynchrones (batching automatique). Un point important : juste après l'appel de la fonction set, la variable d'état contient encore son ancienne valeur, car la mise à jour prend effet au rendu suivant.
setCount(count + 1);
console.log(count); // toujours l'ancienne valeur — le rendu n'a pas encore eu lieu
Modèles pratiques avec tableaux et objets
La règle est la même avec un état complexe : copier, modifier, set. Modèles courants :
- Ajouter un élément à une liste :
setItems(prev => [...prev, nouveau]) - Retirer d'une liste :
setItems(prev => prev.filter(i => i.id !== id)) - Mettre à jour un élément :
setItems(prev => prev.map(i => i.id === id ? { ...i, done: true } : i))
Si ton état se ramifie beaucoup et contient de nombreux champs interdépendants, useReducer peut être plus lisible que useState. Mais pour la plupart des tâches quotidiennes, useState suffit et reste la solution la plus simple.
Questions fréquentes
Quelle est la différence entre useState et useRef ?
useState re-rend le composant lorsqu'il change ; c'est pour les données qui doivent apparaître à l'écran. useRef stocke une valeur entre les rendus mais ne déclenche pas de rendu quand elle change ; on l'utilise pour accéder à un élément du DOM ou pour conserver une valeur qui n'affecte pas le rendu.
Puis-je appeler useState dans une boucle ou une condition ?
Non. Tous les Hooks doivent être appelés au niveau supérieur du composant, dans le même ordre à chaque rendu. Les appeler de façon conditionnelle ou dans une boucle casse la capacité de React à associer les états et provoque une erreur.
Et si la valeur initiale est un calcul coûteux ?
Au lieu de passer la valeur initiale directement, passe une fonction : useState(() => calculCouteux()). Cet « initialiseur paresseux » ne s'exécute qu'au premier rendu et n'est pas recalculé aux rendus suivants.
Bien gérer l'état est le fondement d'une interface facile à maintenir. Si tu développes un projet basé sur React ou souhaites améliorer une interface existante, contacte-moi — construisons ensemble une architecture solide.