11

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.

ThemeProvider App Nav useContext()
💡 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.

Prop Drilling (problème)
// 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

Pattern complet : Context + Provider + Hook
// 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;
}
App.jsx — Wrapping avec le Provider
// 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>
    );
}
Context + useReducer (Redux-like)
// 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.