Maxpaths
Architecture

Redux vs React Context vs Zustand : quel state management choisir ?

Comparaison technique de trois approches de gestion d'etat en React : Redux Toolkit, React Context API, et Zustand. Criteres de choix, exemples de code, et recommandations par taille de projet.

Maxime
14 min de lecture
#React#State Management#Redux#Zustand#Context API

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.

context/auth-context.tsxtsx
1// React Context : solution native
2import { 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 Provider
26 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 validation
36export 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.

Probleme de re-render avec Contexttsx
1// Probleme : re-render de TOUS les consumers
2const 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 change
20// Meme si seul 'theme' l'interesse, un ajout au panier le re-rend
21function 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.

store/cart-slice.tstsx
1// Redux Toolkit : createSlice + configureStore
2import { 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 automatiquement
41export 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.

store/cart-store.tstsx
1// Zustand : store minimaliste et performant
2import { 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 wrapper
30function CartTotal() {
31 // Selecteur : ce composant ne re-rend QUE quand 'total' change
32 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 jamais
38 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

React Context
Solution native pour partager des donnees dans l'arbre de composants.
Avantages
  • Zero dependance, integre a React
  • API simple et connue de tous
  • Parfait pour les donnees a faible frequence de changement
Inconvenients
  • Re-render de tous les consumers a chaque changement
  • Pas de selecteurs natifs
  • Performance degradee avec des updates frequents
  • Provider hell si multiple Contexts
Cas d'usage
  • Theme, langue, authentification
  • Configuration globale
  • Petites applications
Redux Toolkit
Ecosysteme complet avec middleware, DevTools et conventions strictes.
Avantages
  • DevTools puissants (time-travel debugging)
  • Middleware riche (thunks, listeners, RTK Query)
  • Ecosysteme mature et documente
  • Patterns previsibles pour les grandes equipes
Inconvenients
  • Boilerplate reduit mais toujours present
  • Courbe d'apprentissage significative
  • Bundle ~12kB gzipped
  • Overhead pour les petits projets
Cas d'usage
  • Applications large-scale
  • Etat complexe avec logique metier
  • Besoin de middleware et DevTools
  • Equipes structurees avec conventions
Zustand
Store minimaliste avec selecteurs performants et API simple.
Avantages
  • API minimaliste (~2kB gzipped)
  • Pas de Provider requis
  • Selecteurs integres (re-renders optimises)
  • TypeScript-first avec inference naturelle
  • Compatible React Server Components
Inconvenients
  • DevTools moins puissants que Redux
  • Ecosysteme plus petit
  • Moins de conventions imposees
Cas d'usage
  • Projets de taille moyenne
  • Remplacement de Context pour la performance
  • Etat client-side cible
  • Prototypage rapide et iterations

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.

Merci d'avoir lu cet article