Introduction
La gestion d'etat est l'un des sujets les plus debattus dans l'ecosysteme React. Chaque projet finit par se poser la meme question : comment partager et synchroniser l'etat entre les composants ? Trois approches dominent le paysage en 2026 : la solution native React Context, l'ecosysteme etabli Redux Toolkit, et le minimaliste Zustand.
Chacune repond a des besoins differents, et le choix depend rarement d'une superiorite technique absolue. Il depend du contexte : taille du projet, complexite de l'etat, frequence des mises a jour, et preferences de l'equipe.
Trois philosophies, trois trade-offs
Chaque solution repond a un besoin specifique dans le spectre de la gestion d'etat.
- React Context : solution native, zero dependance, concue pour partager des donnees qui changent rarement (theme, locale, auth)
- Redux Toolkit : ecosysteme complet avec middleware, DevTools, et patterns bien documentes pour les applications complexes
- Zustand : store minimaliste (~2kB) avec selecteurs performants, sans Provider, et une API volontairement simple
React Context API
React Context est integre directement dans React. Il permet de passer des donnees a travers l'arbre de composants sans prop drilling. Le pattern classique repose sur un Provider qui encapsule un sous-arbre et des consumers (via useContext) qui lisent la valeur.
1// React Context : solution native2import { createContext, useContext, useState, useMemo } from 'react';3
4interface AuthContextType {5 user: User | null;6 login: (credentials: Credentials) => Promise<void>;7 logout: () => void;8}9
10const AuthContext = createContext<AuthContextType | null>(null);11
12export function AuthProvider({ children }: { children: React.ReactNode }) {13 const [user, setUser] = useState<User | null>(null);14
15 const login = async (credentials: Credentials) => {16 const user = await authService.login(credentials);17 setUser(user);18 };19
20 const logout = () => {21 authService.logout();22 setUser(null);23 };24
25 // useMemo pour eviter les re-renders inutiles du Provider26 const value = useMemo(() => ({ user, login, logout }), [user]);27
28 return (29 <AuthContext.Provider value={value}>30 {children}31 </AuthContext.Provider>32 );33}34
35// Hook personnalise avec validation36export function useAuth() {37 const context = useContext(AuthContext);38 if (!context) {39 throw new Error('useAuth doit etre utilise dans un AuthProvider');40 }41 return context;42}Context brille pour les donnees qui changent rarement : theme, langue, utilisateur connecte, configuration globale. Son integration native evite toute dependance externe et reste familiere pour tout developpeur React.
Les limites de Context
Le probleme principal de Context est la performance : tous les composants qui consomment un Context se re-rendent quand la valeur change, meme s'ils n'utilisent qu'une partie de cette valeur. Il n'y a pas de selecteurs natifs.
1// Probleme : re-render de TOUS les consumers2const AppContext = createContext<AppState>(defaultState);3
4function AppProvider({ children }: { children: React.ReactNode }) {5 const [state, setState] = useState<AppState>({6 user: null,7 theme: 'light',8 notifications: [],9 cart: { items: [], total: 0 },10 });11
12 return (13 <AppContext.Provider value={{ state, setState }}>14 {children}15 </AppContext.Provider>16 );17}18
19// Ce composant se re-rend quand N'IMPORTE QUELLE partie du state change20// Meme si seul 'theme' l'interesse, un ajout au panier le re-rend21function ThemeToggle() {22 const { state, setState } = useContext(AppContext);23 // ^-- re-render a chaque changement de notifications, cart, user...24 return (25 <button onClick={() => setState(s => ({ ...s, theme: s.theme === 'light' ? 'dark' : 'light' }))}>26 {state.theme}27 </button>28 );29}La solution classique consiste a diviser en plusieurs Contexts (ThemeContext, CartContext, NotificationContext...), mais cela cree un "Provider hell" avec de nombreux Providers imbriques en haut de l'arbre. C'est un compromis, pas une solution elegante.
Redux Toolkit
Redux Toolkit (RTK) est la version moderne de Redux. Il simplifie considerablement le boilerplate historique avec createSlice, l'immutabilite via Immer, et un store configure par defaut avec les bons middleware.
1// Redux Toolkit : createSlice + configureStore2import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';3
4interface CartState {5 items: CartItem[];6 total: number;7}8
9const cartSlice = createSlice({10 name: 'cart',11 initialState: { items: [], total: 0 } as CartState,12 reducers: {13 addItem(state, action: PayloadAction<CartItem>) {14 // Immer permet la "mutation" directe (immutable en coulisses)15 state.items.push(action.payload);16 state.total += action.payload.price;17 },18 removeItem(state, action: PayloadAction<string>) {19 const item = state.items.find(i => i.id === action.payload);20 if (item) {21 state.items = state.items.filter(i => i.id !== action.payload);22 state.total -= item.price;23 }24 },25 clearCart(state) {26 state.items = [];27 state.total = 0;28 },29 },30});31
32export const { addItem, removeItem, clearCart } = cartSlice.actions;33
34export const store = configureStore({35 reducer: {36 cart: cartSlice.reducer,37 },38});39
40// Types inferes automatiquement41export type RootState = ReturnType<typeof store.getState>;42export type AppDispatch = typeof store.dispatch;Quand Redux est pertinent
Redux prend tout son sens dans les applications a forte complexite d'etat ou les equipes ont besoin de conventions strictes. Ses atouts principaux :
- DevTools : time-travel debugging, inspection de chaque action, etat courant visualise en temps reel
- Middleware : thunks pour l'asynchrone, listener middleware pour les effets de bord, integration avec RTK Query pour le cache API
- Predictabilite : flux unidirectionnel strict, chaque changement d'etat est tracable via une action
- Ecosysteme : redux-persist, RTK Query, redux-saga, des centaines de middleware communautaires
Le cout : une courbe d'apprentissage, un bundle plus lourd (~12kB gzipped), et un overhead de setup qui n'est pas justifie pour les petits projets.
Zustand
Zustand prend le contre-pied de Redux : pas de Provider, pas de boilerplate, une API minimale. Le store est cree avec une seule fonction create et les composants y souscrivent directement via un hook.
1// Zustand : store minimaliste et performant2import { create } from 'zustand';3
4interface CartStore {5 items: CartItem[];6 total: number;7 addItem: (item: CartItem) => void;8 removeItem: (id: string) => void;9 clearCart: () => void;10}11
12export const useCartStore = create<CartStore>((set) => ({13 items: [],14 total: 0,15 addItem: (item) => set((state) => ({16 items: [...state.items, item],17 total: state.total + item.price,18 })),19 removeItem: (id) => set((state) => {20 const item = state.items.find(i => i.id === id);21 return {22 items: state.items.filter(i => i.id !== id),23 total: state.total - (item?.price ?? 0),24 };25 }),26 clearCart: () => set({ items: [], total: 0 }),27}));28
29// Usage direct : pas de Provider, pas de wrapper30function CartTotal() {31 // Selecteur : ce composant ne re-rend QUE quand 'total' change32 const total = useCartStore((state) => state.total);33 return <span>{total} EUR</span>;34}35
36function AddToCartButton({ item }: { item: CartItem }) {37 // Selecteur stable : reference de fonction, ne change jamais38 const addItem = useCartStore((state) => state.addItem);39 return <button onClick={() => addItem(item)}>Ajouter</button>;40}Pourquoi Zustand seduit
Zustand a gagne en popularite pour plusieurs raisons :
- Taille : ~2kB gzipped, soit 6x moins que Redux Toolkit
- Pas de Provider : le store existe en dehors de l'arbre React, utilisable partout (composants, utilitaires, tests)
- Selecteurs integres : chaque composant souscrit uniquement a la partie du state qui l'interesse, evitant les re-renders inutiles par defaut
- TypeScript-first : inference de types naturelle, pas de types additionnels a maintenir
- Middleware extensible : persist (localStorage), devtools, immer, combine pour des besoins specifiques
Comparaison directe
- Zero dependance, integre a React
- API simple et connue de tous
- Parfait pour les donnees a faible frequence de changement
- Re-render de tous les consumers a chaque changement
- Pas de selecteurs natifs
- Performance degradee avec des updates frequents
- Provider hell si multiple Contexts
- •Theme, langue, authentification
- •Configuration globale
- •Petites applications
- DevTools puissants (time-travel debugging)
- Middleware riche (thunks, listeners, RTK Query)
- Ecosysteme mature et documente
- Patterns previsibles pour les grandes equipes
- Boilerplate reduit mais toujours present
- Courbe d'apprentissage significative
- Bundle ~12kB gzipped
- Overhead pour les petits projets
- •Applications large-scale
- •Etat complexe avec logique metier
- •Besoin de middleware et DevTools
- •Equipes structurees avec conventions
- API minimaliste (~2kB gzipped)
- Pas de Provider requis
- Selecteurs integres (re-renders optimises)
- TypeScript-first avec inference naturelle
- Compatible React Server Components
- DevTools moins puissants que Redux
- Ecosysteme plus petit
- Moins de conventions imposees
- •Projets de taille moyenne
- •Remplacement de Context pour la performance
- •Etat client-side cible
- •Prototypage rapide et iterations
React Context | Redux Toolkit | Zustand |
|---|---|---|
Solution native pour partager des donnees dans l'arbre de composants. | Ecosysteme complet avec middleware, DevTools et conventions strictes. | Store minimaliste avec selecteurs performants et API simple. |
Avantages
| Avantages
| Avantages
|
Inconvenients
| Inconvenients
| Inconvenients
|
Cas d'usage
| Cas d'usage
| Cas d'usage
|
Impact de React 19
React 19 change la donne de plusieurs facons. Les Server Components reduisent le besoin d'etat cote client en deplacant le data fetching sur le serveur. Le nouveau hook use() permet de lire un Context de maniere conditionnelle, ce qui etait impossible avec useContext.
React 19 redefinit les besoins en state management
Moins d'etat client signifie moins de complexite dans le choix de la solution.
- Server Components : les donnees fetchees sur le serveur n'ont plus besoin d'etre dans un store client. TanStack Query ou SWR gerent le cache API, pas Redux.
- Hook use() : permet de lire un Context dans des conditions ou boucles, plus flexible que useContext.
- Actions et useActionState : la gestion des formulaires et mutations server-side est nativement geree, sans store externe.
- Distinction cle : etat serveur (cache de donnees API) vs etat client (UI, preferences, formulaires). Seul l'etat client necessite un store.
Guide de choix par projet
Le choix depend du contexte. Voici un guide pragmatique base sur la taille du projet et la nature de l'etat a gerer :
Recommandations par contexte
Il n'y a pas de solution universelle. Le choix depend de la taille du projet, de l'equipe, et de la nature de l'etat.
- Petite app, peu d'etat partage : React Context suffit. Le theme, l'auth, et quelques preferences globales ne justifient pas une dependance externe.
- App moyenne, etat client frequemment mis a jour : Zustand. Sa legerete, ses selecteurs, et l'absence de Provider en font le choix pragmatique par defaut.
- Large app, logique complexe, equipe structuree : Redux Toolkit. Les DevTools, le middleware, et les conventions imposees sont precieux a cette echelle.
- Etat serveur (cache API) : ni Redux, ni Context, ni Zustand. Utilisez TanStack Query ou SWR, concus specifiquement pour le cache de donnees server.
Un pattern courant en 2026 : Zustand pour l'etat client + TanStack Query pour l'etat serveur. Cette combinaison couvre la grande majorite des besoins sans la complexite de Redux, tout en restant performante et maintenable.
Conclusion
Il n'y a pas de "meilleure" solution de state management. React Context est ideal pour les donnees stables partagees, Zustand pour un store client performant et leger, Redux Toolkit pour les applications complexes necessitant structure et outillage.
Avec React 19 et les Server Components, la tendance est claire : minimiser l'etat client. Deplacez les donnees serveur vers TanStack Query ou les Server Components, et reservez le store client uniquement pour l'etat qui appartient reellement au navigateur : UI state, preferences utilisateur, formulaires en cours.
Le vrai enjeu n'est pas "quel outil choisir" mais "de combien d'etat client ai-je reellement besoin ?". Moins il y en a, plus le choix de l'outil devient simple.