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é ->