Table des matières

Chapitre 4 : useContext

Le problème : Prop Drilling

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

snippet.javascript
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

snippet.javascript
import { createContext } from 'react';
 
const UserContext = createContext(null);

Étape 2 : Fournir la valeur (Provider)

snippet.javascript
function App() {
    const [user, setUser] = useState({ name: 'Jean' });
 
    return (
        <UserContext.Provider value={user}>
            <Layout />
        </UserContext.Provider>
    );
}

Étape 3 : Consommer la valeur (useContext)

snippet.javascript
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 :

snippet.javascript
function Layout() {
    return (
        <div>
            <Header />
            <Main />
        </div>
    );
}
 
function Header() {
    return <Navbar />;
}
 
function Navbar() {
    return <UserMenu />;
}

Exemple complet : Thème

snippet.javascript
// 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;
}
snippet.javascript
// App.js
import { ThemeProvider } from './ThemeContext';
 
function App() {
    return (
        <ThemeProvider>
            <Layout />
        </ThemeProvider>
    );
}
snippet.javascript
// ThemeToggle.js
import { useTheme } from './ThemeContext';
 
function ThemeToggle() {
    const { theme, toggleTheme } = useTheme();
 
    return (
        <button onClick={toggleTheme}>
            Mode actuel : {theme}
        </button>
    );
}
snippet.javascript
// 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 :

snippet.javascript
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 :

Mauvais cas d'usage :

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 :

snippet.javascript
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 :

snippet.javascript
// 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);
}
snippet.javascript
// 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. createContextProvideruseContext
  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é →