Tutoriel React — Chapitre 17

Structurer un projet React proprement

Chapitre 17 — Structurer un projet React proprement

Quand une application grandit, on se retrouve vite à passer des props de parent en enfant, puis en petit-enfant, etc. Cette situation s’appelle le props drilling (forage de props). La Context API permet de partager des données (et des fonctions) à plusieurs composants, sans devoir les transmettre à chaque niveau.

Objectif : comprendre (pas apprendre par cœur) Niveau : débutant absolu Pratique : mini-exercice à la fin

Plan du chapitre

1) Le problème : props drilling

Imaginez que App possède une donnée importante (ex : user) et que vous voulez l’afficher dans un composant très bas dans l’arbre. Sans Context, vous devez passer la prop à chaque niveau.

App (user)
└─ Layout (reçoit user)
   └─ Page (reçoit user)
      └─ ProfileCard (reçoit user)

Ce n’est pas “faux”, mais cela devient pénible et fragile : si vous oubliez une prop à un niveau, tout casse.

2) L’idée du Context

Le Context permet de créer une “boîte” globale (dans une zone de l’app) qui contient une valeur partagée. Tous les composants “dans cette zone” peuvent lire la valeur, sans qu’on doive la passer en props partout.

Concrètement, on déclare un Provider qui fournit des valeurs, et des composants qui consomment ces valeurs avec useContext.

3) Les 3 étapes : createContext → Provider → useContext

Il y a toujours les mêmes étapes :

  1. Créer un context : createContext()
  2. Envelopper une partie de l’app avec un provider : <XProvider>
  3. Lire la valeur dans un composant enfant : useContext(XContext)
import { createContext } from "react";

export const ThemeContext = createContext(null);

4) Exemple simple : thème (clair/sombre)

On va créer un context ThemeContext qui stocke :

  • theme : "light" ou "dark"
  • toggleTheme() : fonction pour changer

A) Créer le context (fichier src/contexts/theme-context.jsx)

import { createContext } from "react";

export const ThemeContext = createContext(null);

B) Fournir le context (dans App.jsx)

import { useState } from "react";
import { ThemeContext } from "./contexts/theme-context.jsx";
import Layout from "./Layout.jsx";

export default function App() {
  const [theme, setTheme] = useState("light");

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

  const value = { theme, toggleTheme };

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

C) Consommer le context (dans un composant enfant)

import { useContext } from "react";
import { ThemeContext } from "./contexts/theme-context.jsx";

export default function ThemeButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button onClick={toggleTheme}>
      Thème actuel : {theme}
    </button>
  );
}

Ici, ThemeButton peut accéder à theme même si Layout ne lui passe aucune prop.

5) Exemple réaliste : utilisateur connecté

Un cas très courant : stocker l’utilisateur connecté (ou null si personne n’est connecté), et fournir des fonctions login / logout.

A) Context “auth”

import { createContext } from "react";

export const AuthContext = createContext(null);

B) Provider (state + fonctions)

import { useState } from "react";
import { AuthContext } from "./contexts/auth-context.jsx";

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

  const login = (email) => {
    setUser({ email });
  };

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

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

C) Consommer

import { useContext } from "react";
import { AuthContext } from "./contexts/auth-context.jsx";

export default function UserBadge() {
  const { user, logout } = useContext(AuthContext);

  if (!user) return <p>Non connecté</p>;

  return (
    <div>
      <p>Connecté : {user.email}</p>
      <button onClick={logout}>Déconnexion</button>
    </div>
  );
}

6) Bonnes pratiques

  • Ne mettez pas tout dans un seul context : créez plusieurs contexts selon les besoins.
  • Le context est idéal pour : thème, langue, utilisateur, panier, préférences UI…
  • Évitez d’y mettre des données qui changent en permanence (cela peut re-render beaucoup).
  • Créez un dossier contexts/ pour centraliser les contexts.

7) Erreurs fréquentes

Erreur : “Cannot destructure property ... of null”

Votre useContext renvoie null car le composant n’est pas dans la zone du Provider. Vérifiez que vous avez enveloppé l’app (ou la page) avec <XContext.Provider>.

Mon context n’est pas à jour

Vérifiez que vous mettez à jour le state via setState (immutabilité), et que la valeur value du Provider contient bien les dernières valeurs.

Beaucoup de re-render

Si le context contient beaucoup de valeurs ou change trop souvent, beaucoup de composants re-render. On peut optimiser (mémoïsation) mais pour débuter, gardez les contexts simples.

8) Résumé (à retenir)

  • Le Context évite le props drilling.
  • Étapes : createContextProvideruseContext.
  • Très utile pour : thème, utilisateur, préférences, etc.
  • Gardez des contexts simples et séparés.

9) Exercice pratique

Créez un context AuthContext pour gérer une connexion “fausse” (sans backend).

Étapes

  • Créer src/contexts/auth-context.jsx avec createContext(null).
  • Créer un AuthProvider qui stocke user dans le state.
  • Créer login(email) et logout().
  • Envelopper votre app avec AuthProvider.
  • Dans une page, afficher “Non connecté” ou l’email + un bouton Déconnexion.

Bonus

Ajoutez une page “Profil” protégée : si user === null, affichez un message “Accès refusé”.

Prochaine étape : Chapitre 18 — useReducer : gérer un state plus complexe (utile avant d’aller vers Redux).