BaliseTonSite

Gestion du State Global

Partage des données entre composants sans prop drilling.

7.1Le problème : prop drilling

Quand plusieurs composants ont besoin de la même donnée (thème, utilisateur connecté...), tu te retrouves à passer les props à travers plein de niveaux :

TSXtsx
// ❌ PROP DRILLING : on passe "user" à travers tout l'arbre
function App() {
  const [user, setUser] = useState({ name: "Alice" })
  return <Layout user={user} />        // ← 1
}

function Layout({ user }) {
  return <Sidebar user={user} />       // ← 2 (ne l'utilise même pas)
}

function Sidebar({ user }) {
  return <UserMenu user={user} />      // ← 3 (ne l'utilise même pas)
}

function UserMenu({ user }) {
  return <p>Bonjour {user.name}</p>    // ← 4 (enfin utilisé !)
}
// Layout et Sidebar servent juste de "passe-plat"

7.2La solution : Context API

Le Context permet de partager des valeurs globalement sans passer par les props :

TSXtsx
import { createContext, useContext, useState } from 'react'

// 1. Créer le Context
const UserContext = createContext<{
  name: string
  setName: (name: string) => void
} | null>(null)

// 2. Créer le Provider (enveloppe l'app)
function UserProvider({ children }: { children: React.ReactNode }) {
  const [name, setName] = useState("Alice")

  return (
    <UserContext.Provider value={{ name, setName }}>
      {children}
    </UserContext.Provider>
  )
}

// 3. Hook custom pour consommer le Context
function useUser() {
  const context = useContext(UserContext)
  if (!context) throw new Error("useUser doit être dans un UserProvider")
  return context
}
TSXtsx
// 4. Utilisation : plus de prop drilling !
function App() {
  return (
    <UserProvider>
      <Layout />
    </UserProvider>
  )
}

function Layout() {
  return <Sidebar />       // ← Plus besoin de passer user !
}

function Sidebar() {
  return <UserMenu />      // ← Zéro props
}

function UserMenu() {
  const { name, setName } = useUser()  // ← Accès direct !
  return (
    <div>
      <p>Bonjour {name}</p>
      <button onClick={() => setName("Bob")}>Changer</button>
    </div>
  )
}

7.3Exemple concret : thème sombre/clair

TSXtsx
import { createContext, useContext, useState } from 'react'

type Theme = "light" | "dark"

const ThemeContext = createContext<{
  theme: Theme
  toggleTheme: () => void
} | null>(null)

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light")

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

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <div className={theme === "dark" ? "bg-slate-900 text-white" : "bg-white text-black"}>
        {children}
      </div>
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) throw new Error("useTheme doit être dans un ThemeProvider")
  return context
}

// Bouton toggle utilisable n'importe où
function ThemeToggle() {
  const { theme, toggleTheme } = useTheme()
  return (
    <button onClick={toggleTheme}>
      {theme === "light" ? "🌙" : "☀️"}
    </button>
  )
}

7.4Combiner Context + useReducer

Pour un state global complexe, combine Context avec useReducer :

TSXtsx
import { createContext, useContext, useReducer } from 'react'

// State & Actions
type CartItem = { id: number; name: string; qty: number }
type CartState = { items: CartItem[] }
type CartAction =
  | { type: "ADD"; payload: CartItem }
  | { type: "REMOVE"; payload: number }
  | { type: "CLEAR" }

function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case "ADD":
      return { items: [...state.items, action.payload] }
    case "REMOVE":
      return { items: state.items.filter(i => i.id !== action.payload) }
    case "CLEAR":
      return { items: [] }
  }
}

// Context
const CartContext = createContext<{
  state: CartState
  dispatch: React.Dispatch<CartAction>
} | null>(null)

export function CartProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(cartReducer, { items: [] })

  return (
    <CartContext.Provider value={{ state, dispatch }}>
      {children}
    </CartContext.Provider>
  )
}

export function useCart() {
  const ctx = useContext(CartContext)
  if (!ctx) throw new Error("useCart doit être dans CartProvider")
  return ctx
}

// Utilisation
function Panier() {
  const { state, dispatch } = useCart()
  return (
    <div>
      <h2>Panier ({state.items.length})</h2>
      {state.items.map(item => (
        <div key={item.id}>
          {item.name}
          <button onClick={() => dispatch({ type: "REMOVE", payload: item.id })}>
            Retirer
          </button>
        </div>
      ))}
      <button onClick={() => dispatch({ type: "CLEAR" })}>
        Vider le panier
      </button>
    </div>
  )
}

Verifie tes acquis

3 questions pour valider ce chapitre

1. Qu'est-ce que le "prop drilling" ?

Valide et sauvegarde ce chapitre

Ne perds pas le fil de ton apprentissage. Chaque QCM terminé sauvegarde ton score. Crée ton profil gratuitement pour débloquer toutes les évaluations du site et retrouver tes résultats plus tard.

Commencer l'aventure
Déjà membre ?Connecte-toi