Context API
Partager l'état global sans Prop Drilling
Le concept : Le flux normal de React force à passer les données de Parent en Enfant (Props). Mais si un composant lointain a besoin d'une info (ex: Le thème sombre, ou l'utilisateur connecté) ? Devoir passer cette Prop sur 10 niveaux s'appelle le Prop Drilling.
Context API résout cela. Considérez-le comme un "nuage de données" planant au-dessus d'une branche de composants (le Provider). Tout composant enfant sous ce nuage peut lever la main (useContext) et attraper la donnée directement.
💡 Le problème : Prop Drilling
Le Prop Drilling se produit quand vous devez faire passer une donnée à travers plusieurs niveaux de composants intermédiaires qui n'en ont pas besoin — juste pour l'atteindre.
// user doit traverser App → Layout → Page → Header → Avatar function App() { const user = { name: "Alice" }; return <Layout user={user} />; } function Layout({ user }) { // Reçoit user mais n'en a pas besoin return <Page user={user} />; } // ... et ainsi de suite sur 4 niveaux
Le Context crée un bus de données global accessible à n'importe quel composant dans l'arbre, sans passer par les intermédiaires.
📦 Créer et utiliser un Context
// contexts/UserContext.js import { createContext, useContext, useState } from 'react'; // 1. Créer le contexte const UserContext = createContext(null); // 2. Créer le Provider (encapsule les composants qui veulent y accéder) export function UserProvider({ children }) { const [user, setUser] = useState(null); const login = (data) => setUser(data); const logout = () => setUser(null); return ( <UserContext.Provider value={{ user, login, logout }}> {children} </UserContext.Provider> ); } // 3. Custom Hook pour consommer le contexte (bonnes pratiques) export function useUser() { const ctx = useContext(UserContext); if (!ctx) throw new Error('useUser doit être dans <UserProvider>'); return ctx; }
// Envelopper l'app avec les Providers function App() { return ( <UserProvider> <ThemeProvider> <Router> <AppRoutes /> </Router> </ThemeProvider> </UserProvider> ); } // Accès direct depuis n'importe quel enfant function Avatar() { const { user, logout } = useUser(); return ( <div> <img src={user.avatar} alt="avatar" /> <button onClick={logout}>Déconnexion</button> </div> ); }
// Pour un state complexe, combiner avec useReducer function reducer(state, action) { switch (action.type) { case 'LOGIN': return { ...state, user: action.payload }; case 'LOGOUT': return { ...state, user: null }; default: return state; } } export function AppProvider({ children }) { const [state, dispatch] = useReducer(reducer, { user: null, theme: 'dark' }); return ( <AppContext.Provider value={{ state, dispatch }}> {children} </AppContext.Provider> ); }
⚡ Performance & Cas d'usage
✅ Bons cas d'usage
- 🔑 Authentification : utilisateur connecté, rôle
- 🎨 Thème : dark/light mode
- 🌍 i18n : langue de l'interface
- 🛒 Panier : état global simple
- 🔔 Notifications : système d'alertes
⚠️ Attention aux performances
Tout changement de valeur du Provider re-rend tous les composants consommateurs, même si leur partie de la valeur n'a pas changé.
Solution : Séparer les contextes (ex: UserContext + UserActionsContext séparés) ou utiliser useMemo pour mémoiser la valeur.
Pour un state complexe : préférer Zustand, Jotai ou Redux Toolkit plutôt que le Context.