Gestion d'état en React
Comprendre la notion d’état
1. Pourquoi parler d’« état » ?
Dans toute application web moderne, et plus encore dans un projet React ou Next.js, l’état occupe une place centrale. Il désigne l’ensemble des données susceptibles d’évoluer au fil du temps et d’impacter directement ce que l’utilisateur voit à l’écran. Ce sont ces données qui rendent une interface vivante.
Concrètement, l’état peut prendre de multiples formes : le texte saisi dans un champ de formulaire, le nombre de clics sur un bouton, les informations d’un utilisateur après sa connexion, le contenu d’un panier d’achats, les filtres appliqués à une liste, ou encore les résultats d’un appel API. Ce sont toutes des données volatiles, modifiables, souvent dépendantes de l’utilisateur ou du contexte de l’application.
Techniquement, dans React, toute modification de l’état entraîne un re-render du composant concerné : la partie de l’interface qui dépend de cette donnée est automatiquement recalculée et réaffichée. C’est ce mécanisme qui permet à React d’offrir des interfaces réactives, dynamiques et cohérentes, sans que le développeur ait à gérer...
Les solutions natives de React
1. Les outils intégrés : useState, useReducer, useContext
React propose nativement plusieurs mécanismes pour gérer l’état d’une application. Le plus élémentaire est le hook useState, qui permet de déclarer un état local à l’intérieur d’un composant. C’est une solution simple et efficace pour capturer une valeur et la faire évoluer au fil du temps, tant que cette logique reste confinée au composant lui-même.
Pour partager cet état entre plusieurs composants imbriqués, React propose le hook useContext. Celui-ci permet de créer un contexte global qui sera accessible par tous les composants descendants. C’est une solution légère, mais qui peut rapidement devenir difficile à maintenir lorsque le contexte commence à gérer trop de responsabilités (authentification, thèmes, préférences, etc.), ou lorsqu’il est surutilisé comme substitut à un véritable store.
Enfin, le hook useReducer constitue une approche plus structurée pour gérer des états complexes, notamment lorsque l’évolution de l’état repose sur des actions explicites. Il s’inspire de la logique de Redux, en internalisant le reducer dans le composant. Cette solution reste néanmoins...
Vers un état global et partageable
1. Pourquoi un état global devient nécessaire
Au fur et à mesure qu’une application Next.js gagne en complexité, elle doit gérer un nombre croissant de données partagées entre plusieurs composants, pages ou layouts. Dans un projet moderne, ces données ne se limitent plus à des états locaux, comme le contenu d’un champ de formulaire ou un compteur. Il devient nécessaire de manipuler des informations plus structurantes, comme le profil d’un utilisateur connecté, ses rôles, ses préférences d’affichage, ou encore l’état d’un panier, d’un système de filtres ou d’un menu interactif.
Dans l’App Router, certaines données peuvent rester disponibles lors des navigations grâce aux layouts persistants, qui ne sont pas recréés à chaque changement de segment. Cette persistance concerne toutefois le runtime client au cours d’une session active. Elle ne garantit pas la conservation des données après un rechargement complet de la page ou lors d’une nouvelle session utilisateur.
Il convient donc de distinguer deux besoins différents :
-
la persistance intra-navigation, assurée par la structure des layouts et le cycle de vie de React ;
-
la persistance inter-session, qui nécessite...
Zustand : la solution simple et moderne
1. Présentation générale de Zustand
Zustand est une librairie de gestion d’état global conçue pour répondre aux besoins des applications React modernes. Elle allie simplicité, performance et une intégration fluide avec l’écosystème React et Next.js, sans les inconvénients des solutions plus lourdes comme Redux.
|
Caractéristiques |
Zustand |
|
Taille |
|~1 kB gzip |
|
API |
Basée sur des hooks |
|
Apprentissage |
Rapide, cohérent avec React |
|
Écosystème |
Compatible avec Next.js et React 18 |
|
Performance |
Excellente : rendus précis |
|
Support du SSR |
Oui, possible via des patterns Next.js |
|
Middlewares intégrés |
Oui : persist, immer, subscribe, etc. |
Zustand permet de créer un store global avec une syntaxe claire, sans configuration complexe. Il n’est pas nécessaire d’utiliser de provider, ni d’écrire des reducers.
2. Exemple minimal et principes de base
// store.ts
import { create } from 'zustand';
type State = {
count: number;
increment: () => void;
};
export const useCounterStore = create<State>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// page.tsx
'use client';
import { useCounterStore } from '@/store';
export default function Home() {
const { count, increment } = useCounterStore();
return <button onClick={increment}>Compteur : {count}</button>;
}
Dans cet exemple, le store expose une variable count et une fonction increment. Le composant accède directement à l’état et le met à jour via le hook, sans aucun boilerplate supplémentaire....
Structurer ses stores avec Zustand
1. Pourquoi structurer ses états en modules ?
L’état constitue le moteur invisible d’une application web moderne. Il contrôle ce qui est affiché, comment l’interface réagit aux actions de l’utilisateur, et dans quelle mesure le comportement du site peut évoluer sans recharger la page. À petite échelle, une approche centralisée peut suffire : l’état est regroupé dans une unique structure globale et tout semble fonctionner correctement. Mais dès que l’application grandit, que les fonctionnalités se multiplient, ou que plusieurs développeurs interviennent sur le même code, cette approche montre rapidement ses limites.
Le code devient plus difficile à lire, car tout y est entremêlé. Une modification anodine dans une portion de l’état peut déclencher des effets de bord inattendus ailleurs dans l’interface. Les composants deviennent dépendants d’un état qui les concerne à peine, ce qui freine leur réutilisation dans d’autres contextes. Et lorsqu’il faut écrire des tests ou corriger un bug, l’absence de structure claire complique inutilement la tâche.
C’est pour éviter ces pièges qu’une approche modulaire s’impose. Avec Zustand, il devient possible de découper l’état global en plusieurs sous-ensembles indépendants, chacun correspondant à une zone fonctionnelle de l’application. Ainsi, l’authentification, l’interface utilisateur, le panier d’achats ou encore le profil utilisateur peuvent chacun disposer de leur propre store, autonome et spécialisé. Cette séparation n’est pas seulement technique : elle reflète une manière de penser l’application comme un ensemble de responsabilités distinctes, coordonnées mais non couplées.
Chaque store peut évoluer à son propre rythme, être testé isolément et ne concerne que les composants qui en ont besoin. Cela allège la charge mentale du développeur, facilite la montée en compétence sur le projet et réduit les risques de régression. En structurant ainsi les états dès...
Intégrer Zustand dans Next.js
1. Points d’attention principaux
L’utilisation de Zustand avec Next.js fonctionne très bien, à condition de respecter quelques règles fondamentales liées au rendu côté serveur et à l’architecture de l’App Router.
|
Sujet |
Recommandation |
|
Server components |
Les stores Zustand sont client-only : ils ne fonctionnent que dans les composants client. |
|
SSR/Hydratation |
En cas de persistance d’état (comme pour un panier), utiliser un middleware comme persist. |
|
Partage entre pages |
Aucun problème : Zustand crée un store global, réactif et accessible dans toute l’application. |
|
Rendu conditionnel |
Les composants qui utilisent un store doivent impérativement être marqués avec ’use client’. |
a. Zustand : usage interdit en Server Components
Zustand repose sur des hooks React (useStore), qui ne peuvent être utilisés que dans des composants client. Il est donc impératif que tout composant accédant à un store Zustand soit précédé de la directive :
'use client';
Cela signifie que :
-
Les stores ne peuvent pas être utilisés dans les server components (composants sans ’use client’).
-
Ils ne peuvent pas être appelés dans les fonctions getStaticProps, getServerSideProps ou dans des loaders serveur.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useCartStore = create(
persist(
(set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}),
{ name: 'cart-storage' }
)
);
Avec ce middleware, les données du store seront stockées dans localStorage et restaurées automatiquement côté client, après l’hydratation.
b. Stores partagés entre pages
Un store Zustand est global au runtime : il est donc automatiquement partagé entre les pages et les composants de l’application. Cela permet de :
-
conserver des données entre...
Comparer Redux, Context et Zustand
1. Objectif de la comparaison
La gestion d’état est un sujet central dans toute application React un tant soit peu interactive. Qu’il s’agisse de stocker des préférences utilisateurs, de synchroniser des données entre composants ou de piloter des comportements d’interface, le choix d’une solution adaptée a un impact direct sur la qualité du code, la maintenabilité du projet et l’expérience utilisateur.
Cette sous-section a pour objectif d’aider le lecteur à faire un choix éclairé et raisonné entre les principales approches de gestion d’état dans l’écosystème React : Redux, React Context et Zustand.
Elle se donne un double objectif :
-
comparer les approches existantes, à la fois sur le plan technique (taille, performance, API, modularité, compatibilité SSR…) et sur le plan pratique (ergonomie, courbe d’apprentissage, facilité de test, cas d’usage types) ;
-
mettre en lumière les avantages spécifiques de Zustand, notamment dans le contexte d’une architecture basée sur Next.js App Router, où la distinction entre composants serveur et client, l’hydratation et le découpage modulaire sont des préoccupations courantes.
Plutôt que de proclamer une solution « meilleure » dans l’absolu, cette sous-section propose une analyse nuancée : chaque outil a ses forces et ses limites, et c’est en fonction du contexte du projet, de la taille de l’équipe, de l’architecture front-end et des besoins métier que le bon choix pourra être opéré.
En définitive, il s’agit de doter le développeur d’une compréhension stratégique lui permettant de répondre à une question simple mais déterminante : « Pourquoi choisir Zustand ici et maintenant ? »
2. Rappels sur Redux et Context
Avant d’entrer dans la comparaison détaillée avec Zustand, il convient de rappeler brièvement les principes de fonctionnement des deux solutions les plus historiquement utilisées pour gérer l’état dans les applications React : Redux et React Context....