React : La communication entre composants
Comment les composants échangent des données : le flux descendant des props, les callbacks pour remonter l'information, le lifting state up entre frères, et le Context pour éviter le prop drilling.
React : La communication entre composants
Jusqu'ici, chaque composant gérait son propre coin de state. Mais une vraie application est un arbre de composants qui doivent s'échanger des données. Comment un enfant prévient-il son parent ? Comment deux frères partagent-ils la même info ? C'est tout l'objet de cette note.
# Le flux des données est descendant
Règle de base de React : les données circulent du parent vers l'enfant, via les props. C'est un flux unidirectionnel (one-way data flow) : l'enfant reçoit, il ne remonte rien tout seul.
Pour envoyer une information vers le haut (enfant → parent), le parent passe une fonction en prop. L'enfant l'appelle ; c'est le parent qui décide quoi en faire.
function Parent() {
function gererClicEnfant(message) {
console.log("L'enfant a dit :", message);
}
return <Enfant onAction={gererClicEnfant} />;
}
function Enfant({ onAction }) {
return <button onClick={() => onAction("coucou")}>Envoyer</button>;
}function Parent() { function gererClicEnfant(message) { console.log("L'enfant a dit :", message); } return <Enfant onAction={gererClicEnfant} />;} function Enfant({ onAction }) { return <button onClick={() => onAction("coucou")}>Envoyer</button>;}Les données descendent (props), les événements remontent (callbacks). Ce sont les deux seuls sens de circulation.
# Faire remonter le state (lifting state up)
Que faire quand deux frères ont besoin de la même donnée ? Un composant ne peut pas lire le state d'un autre. La solution : déplacer le state vers leur plus proche ancêtre commun, puis le redistribuer en props.
function Recherche() {
const [texte, setTexte] = useState(""); // le state vit dans le parent
return (
<>
<ChampRecherche valeur={texte} onChanger={setTexte} />
<Resultat texte={texte} />
</>
);
}
function ChampRecherche({ valeur, onChanger }) {
return <input value={valeur} onChange={(e) => onChanger(e.target.value)} />;
}
function Resultat({ texte }) {
return <p>Tu cherches : {texte}</p>;
}function Recherche() { const [texte, setTexte] = useState(""); // le state vit dans le parent return ( <> <ChampRecherche valeur={texte} onChanger={setTexte} /> <Resultat texte={texte} /> </> );} function ChampRecherche({ valeur, onChanger }) { return <input value={valeur} onChange={(e) => onChanger(e.target.value)} />;} function Resultat({ texte }) { return <p>Tu cherches : {texte}</p>;}Le state est remonté dans Recherche. ChampRecherche le modifie (callback), Resultat le lit (prop). Les deux frères sont synchronisés sans se parler directement : ils passent par le parent. C'est le patron le plus courant pour partager un état.
# Le problème : le prop drilling
Le lifting state up marche bien... tant que l'ancêtre commun est proche. Mais si la donnée doit descendre à travers beaucoup de niveaux, on se retrouve à faire suivre une prop à des composants intermédiaires qui ne s'en servent même pas, juste pour la transmettre plus bas.
App (theme)
└── Layout (reçoit theme, ne l'utilise pas, le passe)
└── Sidebar (reçoit theme, ne l'utilise pas, le passe)
└── Bouton (utilise enfin theme)App (theme) └── Layout (reçoit theme, ne l'utilise pas, le passe) └── Sidebar (reçoit theme, ne l'utilise pas, le passe) └── Bouton (utilise enfin theme)Ce « tuyautage » de props à travers l'arbre s'appelle le prop drilling. C'est verbeux et fragile. Pour les données vraiment transverses, React propose une autre voie.
# Le Context : partager sans faire suivre
Le Context permet à un composant de mettre une valeur à disposition de tous ses descendants, sans la passer de main en main. Trois étapes :
import { createContext, useContext } from "react";
// 1. Créer le contexte (avec une valeur par défaut)
const ThemeContext = createContext("clair");
// 2. Fournir une valeur, en haut de l'arbre
function App() {
return (
<ThemeContext.Provider value="sombre">
<Layout />
</ThemeContext.Provider>
);
}
// 3. Consommer, n'importe où en dessous - sans prop intermédiaire
function Bouton() {
const theme = useContext(ThemeContext);
return <button className={theme}>OK</button>;
}import { createContext, useContext } from "react"; // 1. Créer le contexte (avec une valeur par défaut)const ThemeContext = createContext("clair"); // 2. Fournir une valeur, en haut de l'arbrefunction App() { return ( <ThemeContext.Provider value="sombre"> <Layout /> </ThemeContext.Provider> );} // 3. Consommer, n'importe où en dessous - sans prop intermédiairefunction Bouton() { const theme = useContext(ThemeContext); return <button className={theme}>OK</button>;}Layout et Sidebar n'ont plus rien à transmettre : Bouton lit directement le thème via useContext. Le prop drilling disparaît.
# Context : à utiliser avec parcimonie
Le Context est tentant, mais ce n'est pas une solution universelle :
- Quand la valeur du Provider change, tous les composants qui la consomment se re-rendent. À réserver aux données qui changent peu.
- Ce n'est pas un gestionnaire d'état global (comme Redux ou Zustand) : il transporte une valeur, il n'organise pas la logique.
En pratique, il brille pour le thème, l'utilisateur connecté, la langue - des données globales et stables. Pour du partage local entre quelques composants, le lifting state up vu plus haut reste le bon réflexe.
# À retenir
- Le flux de données est descendant (props) ; pour remonter, le parent passe une fonction que l'enfant appelle.
- Lifting state up : deux frères partagent un état en le remontant vers leur ancêtre commun.
- Le prop drilling (faire suivre une prop à travers des couches inutiles) est le signal qu'on a besoin d'autre chose.
- Le Context partage une valeur avec tous les descendants sans la faire suivre - mais se réserve aux données globales et stables (thème, auth, langue).
Prochaine étape : on quitte le pur React côté client pour aborder ce qui fait la force de Next.js - les Server Components et le rendu côté serveur.