React Context + useReducer
Jusqu'ici, on a géré nos états locaux avec useState. C'est parfait pour un compteur ou l'ouverture d'un menu. Mais quand une donnée doit être accessible partout dans l'application (l'utilisateur connecté, le thème, le panier), passer la donnée de parent en enfant (le prop drilling) devient impossible.
La solution native de React pour ça, c'est de combiner le Context API (pour distribuer la donnée) et useReducer (pour centraliser la logique de mise à jour).
Le fonctionnement de useReducer
useReducer est le grand frère de useState. On l'utilise quand l'état devient complexe (un objet avec plusieurs propriétés) et que la logique de mise à jour dépend de l'état précédent.
Au lieu de modifier l'état directement, on "dispatche" (envoie) une action. Un reducer (une fonction pure) intercepte cette action et retourne le nouvel état.
type CartItem = { id: string; name: string; price: number };
type State = { items: CartItem[]; total: number };
type Action =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'CLEAR_CART' };
function cartReducer(state: State, action: Action): State {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.payload] };
case 'CLEAR_CART':
return { items: [], total: 0 };
default:
return state;
}
}Dans le composant, on utilise le hook useReducer.
const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
/* On modifie l'état en envoyant une action */
<button onClick={() => dispatch({ type: 'CLEAR_CART' })}>Vider</button>Le piège du Context + Reducer
Pour rendre cet état global on doit créer un <CartContext.Provider> et l'entourer autour de notre application.
C'est la méthode "historique" (fortement inspirée de Redux), mais elle souffre de gros défauts de maintenabilité :
- Le Boilerplate : Pour ajouter la moindre petite fonctionnalité, on doit modifier le type de l'action, rajouter un
casedans leswitchdu reducer, modifier le contexte, etc. - Le Provider Hell : Si on a 10 états globaux différents, notre
App.tsxse retrouve encapsulé dans 10 Providers imbriqués. - Le cauchemar des performances : Le Context API de React n'a pas de système de "sélecteur". Si le
totaldu panier change, TOUS les composants qui consomment leCartContextvont se re-rendre, même s'ils n'avaient besoin que de la liste desitems.
Exercice
L'idée c'est de reprendre le projet de panier e-commerce géré de manière "traditionnelle" avec useContext et useReducer.
Il faut ajouter une simple fonctionnalité : pouvoir supprimer un article spécifique du panier.
Vous devrez :
- localiser le fichier contenant le reducer du panier
- mettre à jour les types TypeScript pour autoriser une nouvelle action
REMOVE_ITEMqui prendra l'ID du produit enpayload - ajouter le
casecorrespondant dans l'instructionswitchdu reducer - filtrer le tableau
itemssans muter l'état directement - câbler le bouton "Supprimer" dans l'item dans le sidebar