ITSkillsCenter
Blog

React state management en 2026 : Zustand, Jotai, Redux, Context

10 min de lecture

Lecture : 13 minutes · Niveau : intermédiaire · Mise à jour : avril 2026

Le state management React a beaucoup évolué. L’époque où Redux était la seule réponse sérieuse est révolue : aujourd’hui plusieurs solutions coexistent, chacune avec ses forces. Choisir intelligemment selon le besoin réel évite à la fois la surenchère technique (Redux pour 3 useState) et le bricolage (Context partout pour de l’état complexe). Ce guide compare les options pertinentes en 2026 (informations vérifiées en avril 2026, susceptibles d’évoluer).

La diversité des solutions reflète une réalité : il n’existe pas de « meilleur outil » universel. Une équipe expérimentée habituée à Redux peut en tirer beaucoup de valeur, là où la même équipe forcée à Zustand n’y verrait qu’un changement gratuit. Inversement, une équipe qui démarre avec Redux paye un coût d’apprentissage significatif là où Zustand serait productif en quelques heures. La bonne question n’est pas « quel outil est le meilleur » mais « quel outil convient à ma situation, mon équipe, et mon projet ». Ce guide donne les éléments pour répondre, sans dogme.

Voir aussi → React pour PME : guide frontend pro.


Sommaire

  1. Distinguer état UI et état serveur
  2. useState et useReducer : où sont leurs limites
  3. Context API : utile mais limité
  4. Zustand : simple et efficace
  5. Jotai : atomes et composabilité
  6. Redux Toolkit : robuste et structuré
  7. TanStack Query : pour l’état serveur
  8. Comparatif et recommandation par cas
  9. FAQ

1. Distinguer état UI et état serveur

L’erreur la plus fréquente est de mettre dans le même store des états très différents. Deux familles à séparer :

État UI : ce qui ne vit que côté client. Modal ouvert/fermé, onglet sélectionné, valeur d’un formulaire en cours de saisie, thème, mode édition. Données éphémères, pertinentes uniquement pour l’utilisateur courant.

État serveur : données qui viennent d’une API et doivent rester synchrones avec celle-ci. Liste de clients, détails d’une commande, profil utilisateur. Caractéristiques : revalidation, cache, retry, dédoublonnage.

Ces deux familles ont des besoins fondamentalement différents. Mélanger les deux dans un même outil produit du code complexe. La solution moderne : un outil dédié pour chaque famille.

  • État UI : Zustand, Jotai, Redux Toolkit, Context, ou simplement useState
  • État serveur : TanStack Query, SWR, Apollo Client (GraphQL)

2. useState et useReducer : où sont leurs limites

useState et useReducer couvrent énormément de cas. Tant que l’état est local au composant ou partagé entre 2-3 composants par lift state up, ils suffisent largement.

function Parent() {
  const [filter, setFilter] = useState("");
  return (
    <>
      <SearchBar value={filter} onChange={setFilter} />
      <List items={data.filter(d => d.includes(filter))} />
    </>
  );
}

Quand ça commence à coincer :

  • L’état doit être partagé entre composants éloignés dans l’arbre (prop drilling sur 5+ niveaux)
  • Plusieurs composants frères ont besoin d’écrire et lire le même état
  • L’état devient complexe avec beaucoup de sous-valeurs et transitions
  • Les composants enfants doivent réagir à des actions globales (notifications, login/logout)

À ce point, sortir l’état du composant et l’externaliser dans un store global ou dans le Context.


3. Context API : utile mais limité

const ThemeContext = createContext<"light" | "dark">("light");

function App() {
  const [theme, setTheme] = useState<"light" | "dark">("light");
  return (
    <ThemeContext.Provider value={theme}>
      <Page />
    </ThemeContext.Provider>
  );
}

function Page() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>...</div>;
}

Cas d’usage idéal : valeurs qui changent peu (thème, langue, utilisateur connecté). Lire dans plein de composants sans prop drilling.

Limite : tout consommateur du Context se re-render à chaque changement de valeur. Pour des valeurs qui changent fréquemment ou pour des structures complexes, c’est un problème de performance.

// Anti-pattern : tout l'état applicatif dans un seul Context
const AppContext = createContext({
  user: null,
  cart: [],
  notifications: [],
  preferences: {},
});
// Ajouter une notification rerender toute l'app

Pour un état applicatif riche, sortir vers Zustand ou équivalent.

Pattern : multiple contexts

Découper en plusieurs contexts ciblés réduit l’impact des rerenders. Mais la mécanique reste lourde par rapport à un store dédié.


4. Zustand : simple et efficace

Zustand (github.com/pmndrs/zustand) est devenu le choix par défaut pour beaucoup d’équipes en 2026. ~3kb, API minimaliste, pas de Provider, pas de boilerplate.

import { create } from "zustand";

interface CartStore {
  items: CartItem[];
  add: (item: CartItem) => void;
  remove: (id: string) => void;
  clear: () => void;
  total: () => number;
}

const useCart = create<CartStore>((set, get) => ({
  items: [],
  add: (item) => set(state => ({ items: [...state.items, item] })),
  remove: (id) => set(state => ({ items: state.items.filter(i => i.id !== id) })),
  clear: () => set({ items: [] }),
  total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));

// Usage
function CartIcon() {
  const count = useCart(s => s.items.length);
  return <Badge count={count} />;
}

function CartButton() {
  const add = useCart(s => s.add);
  return <button onClick={() => add(item)}>Ajouter</button>;
}

Bénéfices

  • Pas de Provider à configurer
  • Sélecteurs : on ne re-render que si la portion sélectionnée change
  • Actions et state co-localisés dans le store
  • TypeScript très bien intégré
  • Middleware : persist, devtools, immer, etc.

Persist sur localStorage

import { persist } from "zustand/middleware";

const useCart = create(persist<CartStore>((set) => ({
  // ...
}), { name: "cart" }));

Une ligne pour persister automatiquement.

Structure pour gros projets

Pour des applications avec beaucoup d’état, créer plusieurs stores ciblés (un par domaine) est plus sain qu’un mega-store. useCart, useAuth, useNotifications, usePreferences.


5. Jotai : atomes et composabilité

Jotai (jotai.org) propose un modèle inspiré de Recoil : l’état est découpé en petits atoms, et les composants s’abonnent aux atoms qu’ils utilisent.

import { atom, useAtom } from "jotai";

const filterAtom = atom("");
const itemsAtom = atom<Item[]>([]);

const filteredItemsAtom = atom(get => {
  const filter = get(filterAtom);
  const items = get(itemsAtom);
  return items.filter(i => i.name.includes(filter));
});

function SearchBar() {
  const [filter, setFilter] = useAtom(filterAtom);
  return <input value={filter} onChange={e => setFilter(e.target.value)} />;
}

function ItemList() {
  const [filtered] = useAtom(filteredItemsAtom);
  return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}

Bénéfices

  • Composition naturelle : combiner des atoms pour créer des atoms dérivés
  • Granularité fine : seuls les composants qui dépendent de l’atom modifié re-render
  • Léger : ~3kb
  • Async natif : un atom peut être une promesse

À considérer si

L’application a beaucoup d’état dispersé qui se compose, ou si le modèle Recoil parle naturellement à l’équipe. Pour une PME démarrant, Zustand est souvent plus immédiat.


6. Redux Toolkit : robuste et structuré

Redux Toolkit (redux-toolkit.js.org) est l’évolution moderne de Redux : moins de boilerplate, immer intégré, syntaxe TypeScript fluide.

import { createSlice, configureStore } from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: "cart",
  initialState: { items: [] as CartItem[] },
  reducers: {
    add(state, action: PayloadAction<CartItem>) {
      state.items.push(action.payload);  // immer permet la "mutation"
    },
    remove(state, action: PayloadAction<string>) {
      state.items = state.items.filter(i => i.id !== action.payload);
    },
    clear(state) {
      state.items = [];
    },
  },
});

const store = configureStore({
  reducer: { cart: cartSlice.reducer },
});

// Usage
import { useSelector, useDispatch } from "react-redux";

function Cart() {
  const items = useSelector(state => state.cart.items);
  const dispatch = useDispatch();
  return (
    <button onClick={() => dispatch(cartSlice.actions.clear())}>
      Vider ({items.length})
    </button>
  );
}

Quand le préférer

  • Équipe déjà habituée à Redux
  • Application avec logique métier très complexe (multi-équipes, multi-applications)
  • Besoin du DevTools Redux étendu (time-travel debugging)
  • Middleware tiers spécifiques (saga, observable, listener middleware)

Quand l’éviter

  • Petit projet : trop de boilerplate
  • Équipe junior : courbe d’apprentissage plus raide

Pour une nouvelle PME en 2026 sans bagage Redux, Zustand est plus rentable.


7. TanStack Query : pour l’état serveur

TanStack Query (tanstack.com/query) gère l’état serveur de manière déclarative.

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

function useClients() {
  return useQuery({
    queryKey: ["clients"],
    queryFn: () => fetch("/api/clients").then(r => r.json()),
    staleTime: 60_000,
  });
}

function useCreateClient() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (client: NewClient) =>
      fetch("/api/clients", { method: "POST", body: JSON.stringify(client) }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["clients"] }),
  });
}

function ClientsPage() {
  const { data, isLoading, error } = useClients();
  const createClient = useCreateClient();

  if (isLoading) return <Spinner />;
  if (error) return <ErrorMsg />;
  return (
    <>
      <List items={data} />
      <button onClick={() => createClient.mutate({ nom: "Nouveau" })}>+</button>
    </>
  );
}

Ce que TanStack Query fait pour vous

  • Cache : pas de re-fetch si la donnée est fraîche
  • Background refetch : rafraîchit silencieusement à intervalle ou au focus
  • Retry automatique sur erreur
  • Dédoublonnage : 10 composants demandent la même query → une seule requête
  • Invalidation ciblée après mutation
  • Pagination, infinite scroll : APIs prêtes
  • Devtools dédiés

Combiner avec un autre outil

TanStack Query gère l’état serveur. Pour l’état UI (modals, formulaires en cours), utiliser useState ou Zustand en parallèle. Le combo Zustand + TanStack Query couvre 95% des besoins React modernes.


8. Comparatif et recommandation par cas

Cas Recommandation
App très simple, état local seulement useState / useReducer
Thème, user connecté, langue Context
App moyenne, état UI partagé Zustand
App avec beaucoup d’atoms composables Jotai
Grosse app avec besoins Redux spécifiques Redux Toolkit
Données serveur (n’importe quelle taille app) TanStack Query

Recommandation par défaut PME 2026

  • Zustand pour l’état UI globalement partagé
  • TanStack Query pour les données serveur
  • useState/useReducer pour le local
  • Context uniquement pour les valeurs vraiment globales et stables (thème, user)

Cette stack couvre la quasi-totalité des cas d’une PME francophone, demande peu de boilerplate, scale bien avec la croissance de l’application sans nécessiter de refonte structurelle.

Voir aussi → React performance optimisation pour les implications performance des choix de state management.


9. FAQ

Faut-il migrer un projet Redux existant vers Zustand ?

Non si Redux fonctionne bien. La migration coûte du temps de développement sans gain métier direct. À envisager seulement si l’équipe se plaint régulièrement du boilerplate Redux ou si une refonte majeure est de toute façon planifiée.

Context et performance : c’est vraiment problématique ?

Pour des valeurs qui changent rarement (thème, user) : aucun problème. Pour de l’état qui change toutes les secondes (compteur, état d’un éditeur) : tous les consommateurs re-render à chaque changement, ce qui peut être lent. Sortir vers Zustand qui re-render uniquement les composants utilisant la valeur changée.

Peut-on combiner plusieurs solutions ?

Oui et c’est même recommandé. Zustand pour l’état UI global, useState pour le local, TanStack Query pour les données serveur, Context pour quelques valeurs stables. Chaque outil pour son cas d’usage.

Comment partager Zustand entre fenêtres/onglets du navigateur ?

Avec le middleware zustand/middleware/sync-tabs ou en utilisant BroadcastChannel. Utile pour des cas comme « connexion/déconnexion synchronisée entre onglets ».

Optimistic updates avec TanStack Query ?

Pattern dédié : utiliser onMutate pour mettre à jour le cache avant la réponse serveur, onError pour rollback si échec. Parfait pour des actions où la latence ressentie compte (like, ajout panier).

Zustand sur Server Components ?

Les stores Zustand sont des hooks, donc utilisables uniquement côté client. Pour Next.js avec RSC : créer le store dans des Client Components (avec "use client"), passer les valeurs initiales depuis les Server Components.

Est-ce que Recoil est encore pertinent ?

Recoil est en pause de développement depuis 2024. Jotai propose un modèle similaire activement maintenu. Pour de nouveaux projets, préférer Jotai.


Articles liés (cluster React)


Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité