Retour au cours

frontend / react

Patterns avancés : render props et HOC

Leçon 161 exercice

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.

PatternAnnée de popularitéStatut aujourd'hui
HOC~2015-2018Encore utile pour le routage protégé, sinon dépassé
Render props~2017-2019Largement remplacé par les hooks custom
Hooks customDepuis 2019Approche 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.

jsx
// 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>
jsx
// 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={[]} />
jsx
// 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>;
}
jsx
// 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

1 disponible
1

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.

Résoudre l’exercice →