Résoudre le prop drilling et gérer le state global
Utilisez les flĂšches, cliquez ou glissez pour naviguer
1. Comprendre le prop drilling
Le problĂšme des props Ă travers 5 niveaux
2. Context API
Créer, fournir et consommer un contexte
3. useReducer
Gérer un state complexe avec des actions
4. Pattern Context + useReducer
La solution ultime pour le state global
Le problĂšme : Prop Drilling
Passer des props Ă travers 5 composants... et souffrir
La solution : Context API
createContext, Provider, useContext
useReducer : le state intelligent
Redux simplifié dans votre composant
Pattern : Context + useReducer
La combinaison parfaite pour le state global
Demo live : Cart avec Context
Construire un panier d'achat ensemble
// App possĂšde l'utilisateur
function App() {
const user = { name: "Alice" };
return <Layout user={user} />;
}
function Layout({ user }) {
// Layout ne fait que transmettre!
return <Sidebar user={user} />;
}
function Sidebar({ user }) {
// Sidebar ne fait que transmettre!
return <UserMenu user={user} />;
}
function UserMenu({ user }) {
// Enfin! On utilise la prop!
return <span>{user.name}</span>;
}
ProblĂšme : Layout et Sidebar ne font que transmettre
Si on ajoute un niveau, il faut modifier TOUTE la chaĂźne
Maintenance difficile
Changer le nom d'une prop = modifier tous les composants intermédiaires
Code verbose
Des props inutiles partout, le code devient illisible
Refactoring risqué
Ajouter/retirer un niveau casse la chaĂźne
Solution : Context API - créer un "tunnel" direct
Le "tunnel" qui traverse la hiérarchie
Context permet de transmettre des données à n'importe quel niveau
Sans passer par chaque composant intermédiaire
createContext
Créer le contexte
const MyContext =
createContext();
Provider
Fournir la valeur
<MyContext.Provider
value={data}>
useContext
Consommer la valeur
const value =
useContext(MyContext);
Ces 3 éléments travaillent ensemble pour créer un "tunnel" de données
import { createContext } from 'react';
// Créer un contexte avec une valeur par défaut
const ThemeContext = createContext('light');
La valeur par défaut est utilisée si aucun Provider n'existe
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Layout />
</ThemeContext.Provider>
);
}
Tous les enfants de Provider peuvent accéder à la valeur
import { useContext } from 'react';
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
className={theme === 'dark' ? 'dark-btn' : 'light-btn'}
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
);
}
Le composant accĂšde directement au contexte, sans props!
Ăviter l'import direct
const { theme } =
useContext(ThemeContext);
Répétitif et sujet aux erreurs
Créer un hook dédié
const { theme } = useTheme();
Import plus simple, erreur claire si hors Provider
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// ThemeContext.jsx
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be within ThemeProvider');
return context;
}
// App.jsx - Envelopper avec le Provider
import { ThemeProvider } from './ThemeContext';
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}
// Header.jsx - Utiliser le hook
import { useTheme } from './ThemeContext';
function Header() {
const { theme, setTheme } = useTheme();
return <button onClick={() => setTheme(...)}>...</button>;
}
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = async (email, password) => {
const response = await fetch('/api/login', {...});
setUser(response.user);
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
AuthContext expose : user, login(), logout()
Tous les composants peuvent accéder à l'authentification
Le state management intelligent
useReducer est une alternative Ă useState
Pour une logique de state complexe
useState
Simple et direct
const [count, setCount]
= useState(0);
Idéal pour : valeur unique, logique simple
useReducer
Structuré et puissant
const [state, dispatch]
= useReducer(reducer,
initialState);
Idéal pour : state complexe, multiples actions
Utiliser useReducer si...
Préférer useState si...
// Redux
store.dispatch({ type: 'INCREMENT' });
// useReducer - mĂȘme pattern!
dispatch({ type: 'INCREMENT' });
useReducer = Redux dans un composant
MĂȘme architecture, sans la complexitĂ©
const [state, dispatch] = useReducer(reducer, initialState);
reducer
Fonction pure
(state, action) => newState
initialState
Valeur initiale
Objet ou primitive
dispatch
Envoyer une action
dispatch({ type: 'X' })
dispatch() envoie une action au reducer
Le reducer calcule le nouveau state et React re-render
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
}
Le reducer est une fonction pure : pas d'effets de bord!
MĂȘme input = mĂȘme output, pas de mutations
Backend
// Events stockés
[{ type: 'USER_CREATED' },
{ type: 'EMAIL_UPDATED' },
{ type: 'USER_DELETED' }]
State reconstruit en replayant les events
Frontend (useReducer)
// Actions dispatchées
dispatch({ type: 'ADD_ITEM' });
dispatch({ type: 'REMOVE_ITEM' });
dispatch({ type: 'CLEAR_CART' });
State recalculé par le reducer
(state, action) => newState
La mĂȘme fonction pure dans les deux cas!
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return initialState;
default: throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
Context + useReducer
La combinaison pour le state global
Context pour la portée, useReducer pour la logique
Context seul
Bien pour des valeurs simples
Mais useState dans le Provider = re-renders fréquents
useReducer seul
Bien pour la logique
Mais limité à un seul composant
Ensemble = puissance maximale
useReducer gĂšre le state, Context le partage partout
const CartContext = createContext();
function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
return (
<CartContext.Provider value={{ state, dispatch }}>
{children}
</CartContext.Provider>
);
}
Le Provider expose state ET dispatch
Les composants peuvent lire le state et envoyer des actions
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return { items: [...state.items, action.payload] };
case 'REMOVE_ITEM':
return { items: state.items.filter(i => i.id !== action.payload) };
case 'CLEAR_CART':
return { items: [] };
default:
return state;
}
}
Actions typiques d'un cart : ADD_ITEM, REMOVE_ITEM, CLEAR_CART
function ProductCard({ product }) {
const { dispatch } = useCart();
const addToCart = () => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
return <button onClick={addToCart}>Ajouter</button>;
}
function CartBadge() {
const { state } = useCart();
return <span>{state.items.length} articles</span>;
}
ProductCard dispatch des actions, CartBadge lit le state
Séparation claire des responsabilités
const CartStateContext = createContext();
const CartDispatchContext = createContext();
function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
return (
<CartStateContext.Provider value={state}>
<CartDispatchContext.Provider value={dispatch}>
{children}
</CartDispatchContext.Provider>
</CartStateContext.Provider>
);
}
Les composants qui dispatchent seulement ne re-renderent pas quand le state change!
Faire
Ăviter
L'erreur
<AppContext.Provider
value={ user, theme,
cart, notifications }>
Un seul contexte pour tout l'app
La solution
<AuthProvider>
<ThemeProvider>
<CartProvider>
{children}
</CartProvider>
</ThemeProvider>
</AuthProvider>
Séparer par domaine
Context API
Tunnel pour les données
createContext, Provider, useContext
useReducer
State management intelligent
reducer, dispatch, actions
Pattern combiné
State global optimal
Context + useReducer
PrĂȘts pour la demo live!