Chapitre 4 : useContext

Le problème : Prop Drilling

Quand des données doivent passer à travers plusieurs niveaux de composants :

function App() {
    const [user, setUser] = useState({ name: 'Jean' });

    return <Layout user={user} />;
}

function Layout({ user }) {
    return (
        <div>
            <Header user={user} />
            <Main user={user} />
        </div>
    );
}

function Header({ user }) {
    return <Navbar user={user} />;
}

function Navbar({ user }) {
    return <UserMenu user={user} />;  // Enfin utilisé ici !
}

function UserMenu({ user }) {
    return <span>{user.name}</span>;
}

Layout et Header ne font que transmettre user sans l'utiliser. C'est le prop drilling.

La solution : Context

Context permet de "téléporter" des données à travers l'arbre de composants.

Étape 1 : Créer le Context

import { createContext } from 'react';

const UserContext = createContext(null);

Étape 2 : Fournir la valeur (Provider)

function App() {
    const [user, setUser] = useState({ name: 'Jean' });

    return (
        <UserContext.Provider value={user}>
            <Layout />
        </UserContext.Provider>
    );
}

Étape 3 : Consommer la valeur (useContext)

import { useContext } from 'react';

function UserMenu() {
    const user = useContext(UserContext);

    return <span>{user.name}</span>;
}

Les composants intermédiaires n'ont plus besoin de passer user :

function Layout() {
    return (
        <div>
            <Header />
            <Main />
        </div>
    );
}

function Header() {
    return <Navbar />;
}

function Navbar() {
    return <UserMenu />;
}

Exemple complet : Thème

// ThemeContext.js
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
    const [theme, setTheme] = useState('light');

    const toggleTheme = () => {
        setTheme(prev => prev === 'light' ? 'dark' : 'light');
    };

    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
}

export function useTheme() {
    const context = useContext(ThemeContext);
    if (!context) {
        throw new Error('useTheme must be used within ThemeProvider');
    }
    return context;
}
// App.js
import { ThemeProvider } from './ThemeContext';

function App() {
    return (
        <ThemeProvider>
            <Layout />
        </ThemeProvider>
    );
}
// ThemeToggle.js
import { useTheme } from './ThemeContext';

function ThemeToggle() {
    const { theme, toggleTheme } = useTheme();

    return (
        <button onClick={toggleTheme}>
            Mode actuel : {theme}
        </button>
    );
}
// Card.js
import { useTheme } from './ThemeContext';

function Card({ children }) {
    const { theme } = useTheme();

    return (
        <div className={`card card-${theme}`}>
            {children}
        </div>
    );
}

Passer des fonctions via Context

Vous pouvez passer des fonctions pour permettre aux enfants de modifier l'état :

const AuthContext = createContext(null);

function AuthProvider({ children }) {
    const [user, setUser] = useState(null);

    const login = async (credentials) => {
        const response = await api.login(credentials);
        setUser(response.user);
    };

    const logout = () => {
        setUser(null);
    };

    return (
        <AuthContext.Provider value={{ user, login, logout }}>
            {children}
        </AuthContext.Provider>
    );
}

function useAuth() {
    return useContext(AuthContext);
}

// Utilisation
function LoginButton() {
    const { user, login, logout } = useAuth();

    if (user) {
        return <button onClick={logout}>Déconnexion</button>;
    }

    return <button onClick={() => login({ email, password })}>Connexion</button>;
}

Quand utiliser Context ?

Bons cas d'usage :

  • Thème (dark/light mode)
  • Utilisateur connecté
  • Langue / internationalisation
  • Configuration globale

Mauvais cas d'usage :

  • État local d'un formulaire
  • Données qui ne concernent que quelques composants proches
  • État qui change très fréquemment (performance)

Context vs Props

Critère Props Context
Données concernent peu de composants ✅ ❌
Données traversent beaucoup de niveaux ❌ ✅
Flux de données explicite ✅ ❌
Configuration globale ❌ ✅

Plusieurs Contexts

Vous pouvez imbriquer plusieurs Providers :

function App() {
    return (
        <AuthProvider>
            <ThemeProvider>
                <LanguageProvider>
                    <Layout />
                </LanguageProvider>
            </ThemeProvider>
        </AuthProvider>
    );
}

Exercices

Exercice 1 : Context de langue

Créer un context pour gérer la langue (fr/en) avec un bouton pour changer.

Solution :

// LanguageContext.js
import { createContext, useContext, useState } from 'react';

const LanguageContext = createContext(null);

const translations = {
    fr: { greeting: 'Bonjour', button: 'Anglais' },
    en: { greeting: 'Hello', button: 'French' }
};

export function LanguageProvider({ children }) {
    const [lang, setLang] = useState('fr');

    const toggleLang = () => {
        setLang(prev => prev === 'fr' ? 'en' : 'fr');
    };

    const t = (key) => translations[lang][key];

    return (
        <LanguageContext.Provider value={{ lang, toggleLang, t }}>
            {children}
        </LanguageContext.Provider>
    );
}

export function useLanguage() {
    return useContext(LanguageContext);
}
// Greeting.js
function Greeting() {
    const { t, toggleLang } = useLanguage();

    return (
        <div>
            <h1>{t('greeting')}</h1>
            <button onClick={toggleLang}>{t('button')}</button>
        </div>
    );
}

Points clés à retenir

  1. Context évite le prop drilling
  2. createContext -> Provider -> useContext
  3. Créer un custom hook (useTheme, useAuth) pour simplifier l'usage
  4. Utiliser pour des données vraiment globales
  5. Ne pas abuser : les props restent souvent plus claires

Récapitulatif du Module 3

Hook Usage Déclenche un re-rendu
useState État local Oui
useEffect Effets de bord (API, events) Non (mais peut modifier l'état)
useRef DOM et valeurs persistantes Non
useContext Données globales Oui (si la valeur change)

← Chapitre précédent | Retour au module | Module suivant : React Avancé ->