Aller au contenu principal

State management en 2026 : quand tu n'as pas besoin de Redux

Beaucoup de projets React installent une bibliothèque de state management avant d'avoir un état à gérer. Cet article classe l'état d'une application en quatre familles, montre quel outil répond à chacune avec du code React et Next.js, et détaille les pièges qui transforment un store global en dette technique dès le troisième mois.

Guides & tutoriels ·
Adel LATIBI
Adel LATIBI

Le Briefing Dev - les ressources et actus de la semaine, droit dans ta boîte chaque vendredi gratuitement.

En vous inscrivant, vous acceptez de recevoir notre newsletter. Désinscription possible à tout moment.

Il existe un réflexe bien installé dans l'écosystème React : dès qu'une application dépasse trois écrans, on ajoute une bibliothèque de gestion d'état. Le tutoriel suivi la veille en utilisait une, les offres d'emploi la mentionnent, la décision se prend en trente secondes et personne ne la remet en question.

Six mois plus tard, le store contient des données qui viennent d'une API, un indicateur de chargement dupliqué à trois endroits, un booléen pour l'ouverture d'une modale, et une fonction de rafraîchissement manuel écrite parce que le cache ne se met plus à jour. Chaque nouvelle fonctionnalité demande de toucher quatre fichiers avant d'afficher une ligne de texte.

La documentation officielle de Redux dit elle-même depuis des années qu'il ne faut pas l'utiliser sans besoin identifié. Reste à savoir ce qu'est un besoin identifié. C'est ce que cet article essaie de rendre décidable, avec une méthode de tri qui tient en quatre catégories.

Le store qui gonfle sans qu'on l'ait décidé

Prenons une application de gestion tout à fait ordinaire : une liste de clients avec recherche et filtres, une fiche détaillée, un formulaire d'édition, un thème sombre et un utilisateur connecté. Sur les projets de reconversion ou de première mission qu'on me demande de relire, le store global contient à peu près ceci :

{
  clients: [],          // vient de l'API
  clientsLoading: false,
  clientsError: null,
  selectedClientId: null,
  searchTerm: "",
  currentPage: 1,
  isModalOpen: false,
  theme: "light",
  user: { id: 12, name: "..." }
}

Neuf entrées dans le même sac, alors qu'elles n'ont ni la même durée de vie, ni la même source de vérité, ni les mêmes besoins de synchronisation. Trois d'entre elles décrivent l'état d'une requête réseau, deux devraient se lire dans l'URL, une n'intéresse qu'un seul composant, et deux seulement méritent d'être globales.

Les symptômes arrivent ensuite dans un ordre assez prévisible. Un utilisateur partage un lien vers une liste filtrée, le destinataire voit la liste non filtrée. Deux onglets ouverts affichent des données divergentes. Un bouton de suppression fonctionne mais la liste ne se met à jour qu'après un rechargement complet, ce qu'on corrige avec un appel de rafraîchissement placé à la main, puis un deuxième ailleurs.

Quatre familles d'état, quatre réponses

Avant de choisir un outil, il faut trier. La question à poser sur chaque valeur : où vit la vérité de cette donnée ?

1. État serveur La vérité est en base RSC, TanStack Query 2. État d'URL Doit être partageable searchParams 3. État local Un seul composant useState 4. Global client Partagé, non persisté Context, Zustand Redux entre en jeu sur la famille 4, quand elle devient grosse et qu'on a besoin de tracer chaque transition d'état. Redux Toolkit
Trier l'état par source de vérité avant de choisir un outil.

1. L'état serveur

Une liste de clients, un profil, un panier stocké côté back : la vérité est dans une base de données, pas dans le navigateur. Ce que tu détiens côté client est une copie potentiellement périmée. La question à traiter n'est donc pas le stockage, c'est le cache, la revalidation, la déduplication des requêtes et l'état de chargement. Un store généraliste ne sait rien faire de tout ça sans code écrit à la main.

2. L'état d'URL

Un terme de recherche, un numéro de page, un onglet actif, un ensemble de filtres. Ces valeurs doivent survivre à un rechargement, tenir dans un lien partagé et fonctionner avec le bouton retour du navigateur. L'URL fait tout ça gratuitement, un store non.

3. L'état local

L'ouverture d'un menu déroulant, le contenu d'un champ pendant la saisie, un survol. Personne d'autre que le composant concerné n'a besoin de le lire. useState couvre cette famille entièrement, et remonter cet état plus haut que nécessaire est la première source de re-rendus inutiles.

4. L'état global client

Le thème, la langue d'affichage, l'ouverture d'un panneau latéral, parfois un panier avant validation. Cette famille est petite dans presque toutes les applications, et c'est la seule où la question du store global se pose sérieusement.

Le code de chaque famille

État serveur avec un Server Component

Dans le App Router de Next.js, un composant serveur récupère les données pendant le rendu. Aucun état de chargement à gérer côté client, aucun store à alimenter.

// app/clients/page.tsx
export default async function ClientsPage() {
  const clients = await db.client.findMany();
  return <ClientList clients={clients} />;
}

Trois entrées du store initial disparaissent d'un coup : les données, l'indicateur de chargement et l'erreur. Le fichier qui les gérait n'existe plus.

État serveur côté client avec TanStack Query

Quand les données doivent se rafraîchir sans navigation, une bibliothèque spécialisée dans le cache fait le travail.

const { data, isPending, error } = useQuery({
  queryKey: ["clients", page],
  queryFn: () => fetch(`/api/clients?page=${page}`).then(r => r.json()),
  staleTime: 30_000,
});

Déduplication des requêtes identiques, revalidation au retour de l'onglet, invalidation ciblée après une mutation : tout cela est fourni. Dans un store généraliste, chacun de ces comportements devient du code que tu écris et que tu maintiens.

État d'URL avec les paramètres de recherche

"use client";
import { useRouter, useSearchParams } from "next/navigation";
 
export function SearchBox() {
  const router = useRouter();
  const params = useSearchParams();
  const term = params.get("q") ?? "";
 
  function update(value: string) {
    const next = new URLSearchParams(params);
    value ? next.set("q", value) : next.delete("q");
    router.replace(`?${next}`);
  }
 
  return <input value={term} onChange={e => update(e.target.value)} />;
}

Le lien devient partageable, le bouton retour retrouve le filtre précédent, et un rechargement de page restitue l'écran à l'identique. Aucun store ne donne ces trois propriétés sans travail supplémentaire.

État global client avec Zustand

Pour la quatrième famille, un store minimal suffit dans la majorité des applications.

import { create } from "zustand";
 
export const useUiStore = create((set) => ({
  theme: "light",
  sidebarOpen: false,
  toggleTheme: () => set(s => ({ theme: s.theme === "light" ? "dark" : "light" })),
  toggleSidebar: () => set(s => ({ sidebarOpen: !s.sidebarOpen })),
}));
 
// dans un composant
const theme = useUiStore(s => s.theme);

La sélection par fonction évite qu'un composant qui lit le thème se re-rende quand la barre latérale s'ouvre. Ce point est le principal avantage pratique d'un store externe sur le Context de React.

Les formulaires depuis React 19

React 19, sorti en décembre 2024 et arrivé au patch 19.2.8 en juillet 2026, a absorbé une partie de ce qu'on écrivait à la main dans les stores : état d'envoi, erreur, mise à jour optimiste.

const [state, formAction, isPending] = useActionState(saveClient, null);
 
return (
  <form action={formAction}>
    <input name="name" />
    <button disabled={isPending}>Enregistrer</button>
    {state?.error && <p>{state.error}</p>}
  </form>
);

Le React Compiler, stable depuis octobre 2025, retire de son côté une bonne partie des useMemo et useCallback qu'on plaçait par précaution. Ces deux évolutions expliquent pourquoi un conseil de 2021 sur la gestion d'état ne s'applique plus tel quel.

Les cas où Redux reste le bon choix

Rien de ce qui précède ne dit que Redux serait dépassé. Redux Toolkit est maintenu, documenté, et il répond très bien à des besoins que les alternatives légères ne couvrent pas :

  • Un état client volumineux, avec des dizaines de transitions et des dépendances croisées entre domaines. Un éditeur de documents, un outil de dessin, un tableau de bord temps réel avec plusieurs flux.
  • Un besoin de traçabilité. Les DevTools Redux rejouent l'historique des actions, ce qui rend un bug reproductible à partir d'un rapport utilisateur. Sur un logiciel métier, cette capacité vaut son coût.
  • Une équipe nombreuse sur la même base de code, où la contrainte structurelle imposée par les slices et les reducers vaut mieux qu'un store libre où chacun invente sa convention.
  • Une application existante déjà construite dessus. Migrer un store Redux qui fonctionne vers autre chose parce que le vent a tourné est rarement rentable, et c'est le genre d'arbitrage évoqué dans l'article sur la rentabilité de la formation aux frameworks.

Mon avis, à prendre pour ce qu'il vaut : installer Redux par défaut dans un projet Next.js App Router démarré en 2026 est un choix daté. Sur une application de gestion classique, le tri par familles laisse rarement plus de deux ou trois valeurs dans la quatrième catégorie, et deux valeurs ne justifient pas une architecture à middleware.

Les pièges classiques

Stocker de l'état dérivé

Garder clients et filteredClients côte à côte crée deux sources de vérité qui finiront par diverger. Une valeur calculable à partir d'une autre se calcule au rendu. La règle vaut aussi pour un total, un compteur ou un booléen de validité.

Synchroniser deux états avec useEffect

Un effet qui lit une valeur pour en écrire une autre déclenche un rendu supplémentaire et introduit un décalage d'une frame. Dans presque tous les cas, la valeur doit être calculée pendant le rendu ou remontée au parent commun.

Un Context unique pour toute l'application

Le Context de React ne propose pas de sélecteur : tout consommateur se re-rend quand la valeur change, même s'il ne lit qu'un champ. Un contexte qui contient à la fois l'utilisateur, le thème et l'état d'un panneau fait travailler l'arbre entier au moindre clic. Deux issues : séparer en plusieurs contextes indépendants, ou passer à un store avec sélecteurs.

Traiter le prop drilling comme une fatalité

Passer une prop sur deux niveaux ne justifie pas un store global. La composition règle souvent le cas : au lieu de faire descendre une donnée à travers des composants intermédiaires, passe l'élément déjà construit en children. Les composants du milieu n'ont plus rien à connaître.

Mettre l'état serveur dans le store global

C'est le piège le plus coûteux, parce qu'il ne se voit pas au début. Tout va bien jusqu'à la première mutation depuis un autre écran, et à partir de là tu écris ta propre invalidation de cache. La gestion de cache est un problème réputé difficile, et le résoudre à moitié dans un coin de reducer coûte plus cher que d'adopter un outil qui le traite. La séparation entre les données persistées et la logique d'affichage rejoint ce qui est décrit dans l'article sur le repository pattern, côté back cette fois.

Se former sur ces choix d'architecture

Ces arbitrages s'apprennent en construisant, pas en lisant des comparatifs. La formation React : développer des interfaces modernes traite les hooks, la composition et la gestion d'état locale sur des cas réels. La formation Next.js : construire des applications web modernes prend la suite avec le App Router, les Server Components et le rendu côté serveur, là où une grande partie de la question du state management se dissout. Le reste du parcours figure dans la catégorie Développement Frontend.

Questions fréquentes

Redux est-il mort ?

Non. Redux Toolkit est activement maintenu et tourne dans un très grand nombre d'applications d'entreprise. Ce qui a changé, c'est son statut de choix par défaut : les besoins qu'il couvrait seul en 2018 se répartissent aujourd'hui entre le rendu serveur, les bibliothèques de cache et les stores légers.

Faut-il apprendre Redux quand on cherche un premier poste ?

Il vaut mieux savoir le lire que savoir le mettre en place de zéro. Beaucoup de bases de code existantes en contiennent, donc comprendre un slice, un reducer et le flux d'une action évite d'être bloqué. La priorité reste les fondamentaux de React : composition, hooks, cycle de rendu.

Zustand ou Context, comment choisir ?

Le Context suffit pour une valeur qui change rarement, comme le thème ou la langue. Dès que la valeur change souvent et que beaucoup de composants la lisent, l'absence de sélecteur dans le Context devient un coût de performance visible, et un store externe s'impose.

Les Server Components suppriment-ils le besoin de state management ?

Ils suppriment une grande partie de la première famille, l'état serveur, qui représente souvent la majorité d'un store existant. Les trois autres familles restent, et toute interaction riche continue de demander de l'état côté client.

Comment migrer un store existant sans tout casser ?

Par extraction progressive, en commençant par l'état serveur : sors une ressource du store, branche-la sur une bibliothèque de cache, supprime les reducers devenus inutiles, puis recommence avec la ressource suivante. Les filtres et la pagination passent ensuite dans l'URL. Ce qui reste à la fin est la mesure du store dont tu avais besoin.

Un projet de portfolio a-t-il besoin d'un store global ?

Rarement. Un recruteur technique repère un store surdimensionné plus vite qu'une absence de store, et savoir expliquer pourquoi tu n'en as pas mis produit un meilleur effet en entretien qu'une configuration recopiée d'un tutoriel.

Un exercice utile sur ton projet actuel : ouvre ton store et note en face de chaque entrée à quelle famille elle appartient. Les lignes que tu n'arrives pas à classer sont en général celles qui posent problème.

Pour la qualité du code autour de ces choix, l'article sur les 20 principes de code donne le cadre général, et celui sur les erreurs de typage qui révèlent un mauvais design traite d'un symptôme voisin côté TypeScript.

Vous êtes expert ?

Partagez votre expertise sur notre blog

Tutoriel, retour d'expérience, analyse - publiez un article invité et gagnez en visibilité.

Écrire pour nous