Qu’est-ce que l’API Context ?
L’API Context est la méthode intégrée à React pour transmettre des données à travers un arbre de composants sans avoir à faire passer des props à chaque niveau. Elle résout un problème courant et spécifique : une valeur dont de nombreux composants ont besoin, mais qui se situe très haut dans l’arbre.
L’exemple classique est le thémage. Un Button situé profondément dans l’arbre a besoin du thème actuel, mais chaque composant entre le sommet et le bouton devrait accepter et transmettre une prop theme. C’est ce qu’on appelle le prop drilling, et Context permet de l’éviter. Vous fournissez la valeur une seule fois, et n’importe quel descendant peut la lire directement.
Création et fourniture du contexte
Le contexte se compose de trois éléments : un objet, un provider et un hook de consommation.
// AuthContext.jsx
import { createContext, useContext, useMemo, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(
() => ({
user,
login: (nextUser) => setUser(nextUser),
logout: () => setUser(null),
}),
[user],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
createContext définit le contexte ainsi qu’une valeur par défaut. Le provider fournit la valeur actuelle à tous les composants enfants. Le hook personnalisé lit cette valeur et renvoie une erreur explicite si le composant est utilisé en dehors du provider.
Consommer le contexte
N’importe quel descendant peut lire la valeur, peu importe sa profondeur dans l’arborescence.
// Header.jsx
import { useAuth } from "./AuthContext";
export function Header() {
const { user, logout } = useAuth();
return (
<header>
<span>{user ? `Hi, ${user.name}` : "Guest"}</span>
{user && <button onClick={logout}>Sign out</button>}
</header>
);
}
Le Header n’a jamais reçu de prop de la part de App. Il est allé chercher la valeur dont il avait besoin directement dans l’arbre, ce qui permet aux composants intermédiaires de ne pas être au courant des données qu’ils n’utilisent pas.
Le contexte n’est pas un état
Cette distinction induit souvent en erreur. Le contexte ne détient pas d’état et ne gère pas les mises à jour. Il transporte une valeur. L’état lui-même réside dans useState ou useReducer, et le provider le transmet aux composants enfants.
C’est pourquoi un pattern courant consiste à coupler le contexte avec un reducer :
// CartContext.jsx
import { createContext, useContext, useReducer } from "react";
const CartContext = createContext(null);
function reducer(state, action) {
switch (action.type) {
case "add":
return [...state, action.item];
case "remove":
return state.filter((item) => item.id !== action.id);
default:
return state;
}
}
export function CartProvider({ children }) {
const [items, dispatch] = useReducer(reducer, []);
return (
<CartContext.Provider value={{ items, dispatch }}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => useContext(CartContext);
Le reducer rend les mises à jour prévisibles, et le contexte les rend accessibles partout sans avoir recours au prop drilling.
Performance : la véritable contrainte
Lorsqu’une valeur de provider change, chaque composant qui lit ce contexte est re-rendu. Il n’existe pas de sélecteur, vous ne pouvez donc pas vous abonner à une seule partie de la valeur. Deux bonnes pratiques permettent de garder cela gérable.
Mémorisez la valeur. Un littéral d’objet crée une nouvelle référence à chaque rendu, ce qui signifie que chaque consommateur est re-rendu même lorsque rien n’a changé.
// memoised
const value = useMemo(() => ({ user, logout }), [user]);
Séparez les contextes. Placez l’état et les actions dans des contextes distincts, ou séparez-les par fonctionnalité, afin qu’un composant ayant seulement besoin de dispatcher une action ne soit pas re-rendu lors d’un changement d’état.
// split.jsx
const CartStateContext = createContext(null);
const CartActionsContext = createContext(null);
Les composants qui appellent uniquement des actions s’abonnent au contexte des actions, dont l’identité ne change jamais ; ils ne sont donc jamais re-rendus suite à une mise à jour de l’état. Cette simple astuce résout la plupart des problèmes de performance liés au contexte.
Quand utiliser Context, et quand l’éviter
Context est l’outil idéal pour :
- Le thème et le mode de couleur.
- L’utilisateur authentifié et la session.
- La locale et les traductions.
- Un routeur ou une petite configuration stable.
Tournez-vous vers un store dédié lorsque vous avez :
- Des mises à jour fréquentes, comme la saisie de texte, le glisser-déposer ou des animations.
- Un arbre d’état volumineux où les composants nécessitent des selectors.
- Un besoin de middleware, de persistance ou d’outils de développement de type “time-travel”.
- Un état serveur qui nécessite du caching et du refetching.
Des bibliothèques comme Zustand et Redux Toolkit ajoutent des selectors pour que les composants s’abonnent à des tranches (slices) plutôt qu’à la valeur complète. Pour les données serveur, TanStack Query gère entièrement le caching.
Bonnes pratiques
- Enveloppez
useContextdans un hook personnalisé et vérifiez l’absence de provider. - Mémoïsez les valeurs du provider avec
useMemo. - Séparez les contextes par responsabilité, en particulier l’état par rapport aux actions.
- Gardez des valeurs de contexte restreintes et ciblées.
- Utilisez
useReducerlorsque les mises à jour sont complexes ou dépendent de l’état précédent. - N’utilisez pas le contexte pour des mises à jour fréquentes ; utilisez un store basé sur des sélecteurs.
- Fournissez des valeurs par défaut cohérentes pour que les composants puissent s’afficher en dehors d’un provider lorsque c’est approprié.
Erreurs courantes
- Traiter le context comme un gestionnaire d’état et s’attendre à avoir des selectors.
- Passer un objet inline comme valeur du provider, provoquant le re-render de tout l’arbre.
- Regrouper des données sans rapport dans un seul context géant.
- Oublier la valeur par défaut et provoquer un crash sur
undefined. - Utiliser le context pour des valeurs dont seuls un ou deux composants ont besoin.
- S’attendre à ce que le context optimise automatiquement les re-renders.
Et après ?
Le Context est le fondement du partage d’état dans React, et connaître ses limites vous permet de savoir quand passer à l’étape supérieure. Comparez-le avec Redux Toolkit pour un état prévisible à grande échelle et Zustand pour un store basé sur des sélecteurs et plus léger, puis gérez vos données serveur avec TanStack Query. Pour Vue, le concept équivalent est abordé dans le guide Pinia.