frontend / react
Patterns avancés : render props et HOC
Explication
Ce que vous allez apprendre
- Lire et comprendre une render prop dans du code React existant
- Lire et comprendre un Higher-Order Component (HOC) et son rôle
- Identifier pourquoi les hooks custom ont largement remplacé ces deux patterns
- Reconnaître le "wrapper hell" provoqué par l'empilement de plusieurs HOC
- Savoir dans quel cas précis un HOC reste pertinent aujourd'hui (routage protégé)
Dans quel contexte ?
En reprenant une base de code vieille de quatre ans, un développeur tombe sur const PageProfilProtegee = withAuth(withLoading(withTheme(PageProfil))); dans src/pages/PageProfil.jsx. Impossible de déboguer facilement dans les DevTools React : trois couches de composants anonymes s'empilent, chacune ajoutant sa propre prop. Comprendre le fonctionnement des HOC est indispensable pour maintenir ce type de code existant, même si un hook custom serait aujourd'hui préféré pour un nouveau développement.
Étape 1 : un problème résolu avant les hooks
Avant l'introduction des hooks en 2019, il n'existait aucun moyen simple de partager une logique à état entre plusieurs composants sans dupliquer du code.
Étape 2 : deux patterns développés pour contourner ça
La communauté React a alors développé deux patterns : les render props et les Higher-Order Components (HOC). Ils restent présents dans du code existant, d'où l'intérêt de savoir les lire.
Étape 3 : qu'est-ce qu'une render prop
Une render prop est une prop dont la valeur est une fonction, à qui le composant délègue la décision de CE QUI doit être affiché.
Étape 4 : un exemple concret
Un composant qui suit la position de la souris peut exposer cette position à travers une fonction reçue en prop, plutôt que d'imposer lui-même un affichage figé.
Étape 5 : qu'est-ce qu'un HOC
Un HOC est une fonction qui prend un composant en entrée et retourne un nouveau composant enrichi en sortie — un peu comme un décorateur.
Étape 6 : pourquoi les hooks les ont remplacés
Ces deux patterns ajoutent des niveaux d'imbrication supplémentaires, ce qui complique la lecture et le débogage. Un hook custom obtient le même résultat de façon bien plus lisible.
Piège fréquent
Empiler plusieurs HOC (withAuth(withLoading(withTheme(Composant)))) crée un "wrapper hell" : dans les DevTools React, l'arbre affiche plusieurs composants anonymes imbriqués, rendant le débogage bien plus difficile qu'avec des hooks custom composés dans une seule fonction.
| Pattern | Année de popularité | Statut aujourd'hui |
|---|---|---|
| HOC | ~2015-2018 | Encore utile pour le routage protégé, sinon dépassé |
| Render props | ~2017-2019 | Largement remplacé par les hooks custom |
| Hooks custom | Depuis 2019 | Approche recommandée par défaut |
Un usage encore pertinent
Les HOC gardent un usage pertinent aujourd'hui pour des préoccupations transverses au niveau du routage, comme protéger une route derrière une vérification d'authentification.
Et ensuite ?
Une fois ces patterns reconnus, la prochaine étape change de terrain : la distinction entre Server Components et Client Components.
Commandes & code
Render props et Higher-Order Components
Deux patterns de partage de logique antérieurs aux hooks, encore présents dans beaucoup de code existant.
// Render prop : un composant reçoit une FONCTION comme prop, qui décide du rendu
function SuiviSouris({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
function gererMouvement(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
return (
<div onMouseMove={gererMouvement} style={{ height: "100vh" }}>
{render(position)}
</div>
);
}
function App() {
return (
<SuiviSouris render={(pos) => (
<p>Souris à {pos.x}, {pos.y}</p>
)} />
);
}
// Variante "children as a function" : même idée, via children
function SuiviSouris2({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
{children(position)}
</div>
);
}
// <SuiviSouris2>{(pos) => <p>{pos.x}, {pos.y}</p>}</SuiviSouris2>// Higher-Order Component (HOC) : une fonction qui prend un composant, en retourne un autre enrichi
function avecChargement(ComposantEnveloppe) {
return function ComposantAvecChargement({ chargement, ...props }) {
if (chargement) return <p>Chargement...</p>;
return <ComposantEnveloppe {...props} />;
};
}
function ListeUtilisateurs({ utilisateurs }) {
return <ul>{utilisateurs.map((u) => <li key={u.id}>{u.nom}</li>)}</ul>;
}
const ListeUtilisateursAvecChargement = avecChargement(ListeUtilisateurs);
// utilisation : <ListeUtilisateursAvecChargement chargement={true} utilisateurs={[]} />// HOC composables : le piège du "wrapper hell" en imbriquant plusieurs HOC
const ComposantFinal = avecAuth(avecChargement(avecTheme(MonComposant)));
// difficile à lire, difficile à débugger dans les DevTools (plusieurs couches anonymes)
// Équivalent en hooks custom : généralement PRÉFÉRÉ aujourd'hui, plus lisible
function MonComposant() {
const { utilisateur } = useAuth();
const { chargement, donnees } = useFetch("/api/donnees");
const theme = useTheme();
if (chargement) return <p>Chargement...</p>;
return <div className={theme}>{/* ... */}</div>;
}// Cas où HOC reste pertinent aujourd'hui : injecter des props transverses au niveau du ROUTAGE,
// par exemple protéger une route entière plutôt qu'un composant interne
function routeProtegee(ComposantPage) {
return function RouteProtegee(props) {
const { utilisateur } = useAuth();
if (!utilisateur) return <Navigate to="/login" />;
return <ComposantPage {...props} />;
};
}
const PageProfilProtegee = routeProtegee(PageProfil);Résumé
- Render props partagent une logique via une fonction passée en prop (ou en
children) qui contrôle le rendu. - Les HOC enrichissent un composant en le retournant enveloppé dans un nouveau composant.
- Les hooks custom ont largement remplacé ces deux patterns pour le partage de logique à état.
- Les HOC restent utiles pour des préoccupations transverses au niveau du routage (garde d'authentification).
Exercices pratiques
Mission : démêler le wrapper hell de PageProfil
Objectif : Décoder une chaîne de HOC empilés et la remplacer par des hooks custom composés, plus lisibles.
Contexte
En reprenant une base de code vieille de quatre ans, tu tombes sur const PageProfilProtegee = withAuth(withLoading(withTheme(PageProfil)));. Dans les DevTools React, l'arbre affiche trois composants anonymes imbriqués avant d'atteindre PageProfil, et une erreur de rendu ne pointe que vers WithLoading(WithTheme(PageProfil)) sans préciser laquelle des trois couches est fautive. La direction technique demande de migrer ce fichier vers des hooks custom avant le prochain sprint.