Contexte, état ou Redux Toolkit : choisir en fonction de la complexité de la transition
Choisissez les outils de gestion de l’état React en fonction de la complexité des transitions et non de la taille de l’application : Context propage les dépendances en arborescence, Zustand affine les abonnements, Redux Toolkit gère les transitions.
Sources: Vibe Coding Can Build the Store. Can You Design It?
L’IA a rendu presque gratuit un aspect du développement frontend : la création de code. Lorsqu’on demande un store Zustand, une slice Redux ou Context associée à useReducer, un modèle génère du code compilable. La syntaxe n’est plus le point difficile ; la véritable difficulté réside dans un schéma d’architecture vide et dans la question de savoir comment l’état doit circuler au sein du produit.
C’est là le véritable problème de gestion d’état. Context API, Zustand et Redux Toolkit l’abordent sous des angles différents. Il ne s’agit pas ici de détails techniques liés aux API, mais de ce qui se trouve en arrière-plan.
1. Context API : l’arbre React constitue la frontière
Context répond à une question précise : comment un valeur peut-elle atteindre des composants profonds sans devoir parcourir chaque niveau via des props ?
La documentation de React décrit useContext comme un mécanisme permettant de lire et d’écouter un contexte. Un composant récupère la valeur du fournisseur le plus proche qui lui correspond. Lorsque la valeur de ce fournisseur change, les composants qui lisent cette valeur sont mis à jour.
Ce qui est important, ce n’est pas l’appel du hook — c’est la relation hiérarchique.
Le fournisseur définit un environnement ; le consommateur lit à partir de cet environnement.
Le contexte existe au sein de la hiérarchie React.
Le contexte ne gère pas l’état par lui-même
Une idée reçue courante est que le contexte constitue une bibliothèque d’état. Les mises à jour proviennent généralement de useState, de réducteurs ou de hooks personnalisés. Le contexte a principalement pour rôle de propager la valeur produite par ces hooks.
Le piège du contexte
Les gens disent : « Le contexte provoque des re-rendus inutiles. » Cette affirmation est trop simpliste. React compare la valeur précédente et suivante d’un fournisseur à l’aide de Object.is. Lorsque la valeur change, tous les lecteurs sont mis à jour ; une limite memo ne bloque pas un nouveau contexte.
Considérons un seul ensemble de champs non liés entre eux :
<AppContext
value={{
user,
cart,
orders,
notifications
}}
>
Le problème plus profond est d’ordre architectural : il existe une seule grande limite de dépendance pour des domaines qui ne devraient pas être regroupés.
Au lieu de :
AppContext
préférez :
AuthContext
ThemeContext
FeatureFlagContext
Chaque contexte aura alors une fonction sémantique claire. Règle de conception : un contexte doit représenter une limite de dépendance significative. Ce n’est pas le contexte en soi qui est « inefficace à grande échelle » — c’est souvent un contexte géant.
2. Quitter l’arborescence : Zustand
Le contexte est lié à l’arbre des composants. Zustand pose une question différente : comment le état partagé du client peut-il exister en dehors de cet arbre avec des abonnements précis ?
Un store peut contenir l’état ainsi que les fonctions qui le modifient :
const useCartStore = create((set) => ({
items: [],
addItem: (item) =>
set((state) => ({
items: [...state.items, item]
})),
removeItem: (id) =>
set((state) => ({
items: state.items.filter(
item => item.id !== id
)
}))
}));
Les composants sélectionnent des parties de l’état :
const items = useCartStore(
state => state.items
);
L’avantage réside dans la frontière des abonnements. Le composant ne demande pas tout le store ; il demande uniquement la partie qui l’intéresse. Les sélecteurs rendent ce contrat explicite.
Pourquoi Redux persiste-t-il
Si Zustand est si simple, pourquoi Redux Toolkit ? Parce que le problème peut passer de « où placer cette valeur ? » à « comment gérer tout ce qui peut la modifier ? »
Imaginez des domaines qui se chevauchent :
Checkout
Payment
Inventory
Orders
Refunds
Permissions
Checkout Started
↓
Payment Started
↓
Payment Succeeded
↓
Inventory Reserved
↓
Order Created
Si la réservation de stock échoue après un paiement réussi, le problème n’est plus lié au stockage — il s’agit d’un problème de transition d’état. C’est là que la structure de Redux devient utile.
3. Redux comme transitions explicites
Il ne faut pas se représenter Redux comme un stockage global. Considérez-le plutôt comme un système explicite permettant de décrire des événements et de calculer les états suivants de manière prévisible :
nextState =
reducer(previousState, action)
Flux Redux
Comparé à une simple instruction set(...), cela semble plus complexe :
set(...)
Chaque étape a encore une fonction précise :
- Action — qu’est-ce qui s’est passé ?
- Reducer — comment l’état doit-il changer ?
- Store — quel est l’état actuel ?
- Selector — de quoi a besoin ce composant ?
Cette explicitité est justement sa caractéristique principale. Lorsque les règles métier deviennent complexes, savoir pourquoi l’état a changé vaut mieux que d’économiser dix lignes de code.
Redux Toolkit, c’est le Redux moderne
Le Redux classique nécessitait des types d’actions et des créateurs manuels. Toolkit simplifie cela grâce à createSlice:
const cartSlice = createSlice({
name: "cart",
initialState: {
items: []
},
reducers: {
addItem(state, action) {
state.items.push(action.payload);
}
}
});
L’état et les transitions associés restent regroupés ; des créateurs d’actions sont générés automatiquement. Immer permet une syntaxe similaire à celle des mutations tout en produisant des mises à jour immuables. Le Redux moderne consiste moins à écrire du code générique et plus à conserver un modèle de transitions structuré, sans les formalités historiques.
Middlewares et tâches asynchrones
Les middlewares se situent entre dispatch et reduce :
C’est dans cette section que se trouvent le journalisation, les analyses, les workflows asynchrones et les réactions aux actions spécifiques. Toolkit ajoute des outils tels que createAsyncThunk et des middleware d’écouteur. Zustand laisse l’organisation asynchrone ouverte ; Toolkit propose des patterns plus établis.
Lequel choisir ?
Évitez l’heuristique basée sur la taille :
- Application petite → Context
- Moyenne → Zustand
- Grosse → Redux
Le nombre d’écrans n’est pas un bon indicateur de la complexité de l’état. Une application CRUD à cent écrans peut être plus simple qu’un flux de paiements et de permissions à dix écrans.
Mieux vaut réfléchir aux relations et transitions :
Modèle mental à retenir
N’essayez pas de mémoriser trois logos. Retenez plutôt trois approches :
- Context — distribuer une dépendance à travers l’arbre React
« Quelle bibliothèque est la meilleure ? » n’est pas la bonne première question. Il vaut mieux se demander : quel type de complexité doit être géré ? Choisissez l’abstraction qui rend cette complexité plus facile à comprendre — et non celle qualifiée de « moderne ».
Dans la pratique, de nombreux projets combinent ces outils : Context pour les thèmes et les paramètres régionaux, Zustand pour les paniers d’achat et les éléments visuels de l’interface, Toolkit uniquement lorsque des processus complexes liés aux paiements ou au stock nécessitent un suivi précis. Cette combinaison est acceptable tant que chaque choix est intentionnel. Les problèmes surviennent lorsque un seul contexte massif ou un seul stock non différencié englobe toutes les fonctionnalités, simplement parce qu’une recommandation suggère une seule bibliothèque pour tout.