Retour au cours

frontend / react

Refs avancées : ref comme prop et fonctions de nettoyage

Leçon 291 exercice

Explication

Ce que vous allez apprendre

  • Recevoir ref comme une prop normale sur un composant fonction depuis React 19
  • Comprendre dans quel cas forwardRef reste nécessaire malgré cette simplification
  • Écrire un ref callback qui retourne une fonction de nettoyage
  • Restreindre l'API exposée à un parent avec useImperativeHandle
  • Fusionner plusieurs refs ciblant le même élément DOM

Dans quel contexte ?

Une équipe migre son design system interne vers React 19 et découvre que la moitié de ses composants (Bouton.jsx, Champ.jsx, Carte.jsx) enveloppent inutilement chaque définition dans forwardRef, un boilerplate systématique ajouté "par habitude" depuis des années. Supprimer ce forwardRef devenu optionnel simplifie chaque fichier de plusieurs lignes, sans changer le comportement des composants pour qui les utilise.

Étape 1 : le problème historique

Depuis la leçon sur useRef, on sait qu'une ref donne accès au noeud DOM réel d'un élément. Mais transmettre cette ref à travers un composant personnalisé, et non un élément natif comme <input>, posait un problème bien connu : ref n'était pas traitée comme une prop ordinaire.

Étape 2 : la solution imposée jusque-là

Il fallait donc obligatoirement envelopper le composant dans forwardRef pour pouvoir la recevoir. Cette étape supplémentaire, facile à oublier, restait une source régulière de confusion, même chez des développeurs expérimentés.

Étape 3 : ce que change React 19

Depuis React 19, ce détour n'est plus nécessaire dans le cas simple. ref peut désormais être reçue directement comme un prop normal sur un composant fonction, ce qui rend forwardRef optionnel.

Étape 4 : ce que ce changement ne change PAS

Il faut bien mesurer la portée de cette évolution : elle ne modifie rien de fondamental sur ce qu'EST une ref. Elle simplifie seulement la mécanique de transmission, pour un code plus direct à lire.

Étape 5 : un second outil, le ref callback

Au-delà de la forme objet la plus courante, une ref peut aussi être une simple fonction, appelée par React avec le noeud DOM concerné dès qu'il est monté : c'est le "ref callback".

Étape 6 : la vraie nouveauté, un cleanup intégré

Voici l'apport concret de React 19 : ce callback peut désormais RETOURNER une fonction de nettoyage, appelée automatiquement au démontage ou au changement de noeud — exactement comme le cleanup d'un useEffect.

Prérequis

Cette leçon suppose la leçon useRef déjà acquise (accès au DOM, forwardRef, useImperativeHandle) : elle en présente uniquement les évolutions apportées par React 19, pas les fondamentaux.

Avant React 19Depuis React 19
forwardRef obligatoire pour recevoir une ref sur un composant customref reçue comme une prop normale
Cleanup d'un ref callback géré manuellement (ex: dans un useEffect séparé)Le ref callback peut retourner directement sa fonction de nettoyage

Restreindre puis fusionner

Il reste deux outils complémentaires à connaître. useImperativeHandle permet d'exposer volontairement une API restreinte au parent plutôt que le noeud DOM complet. Et quand plusieurs refs doivent cibler le même élément — une interne au composant, une externe reçue en prop — un callback custom qui les applique une à une reste nécessaire.

Et ensuite ?

Une fois ces refs avancées maîtrisées, la dernière étape du cours aborde une brique essentielle à la fiabilité d'une application : tester avec Mock Service Worker.

Commandes & code

Refs avancées : ref comme prop et fonctions de nettoyage

React 19 simplifie le passage de ref à travers les composants, et les callbacks de ref gagnent un vrai cleanup.

jsx
// Avant React 19 : forwardRef obligatoire pour recevoir une ref sur un composant custom
const ChampAvant = forwardRef(function ChampAvant(props, ref) {
    return <input ref={ref} {...props} />;
});

// React 19 : ref est un prop comme un autre, plus besoin de forwardRef pour le cas simple
function ChampApres({ ref, ...props }) {
    return <input ref={ref} {...props} />;
}

function Formulaire() {
    const inputRef = useRef(null);
    return <ChampApres ref={inputRef} placeholder="Email" />;
    // fonctionne directement, sans envelopper ChampApres dans forwardRef
}
jsx
// Ref callback : fonction appelée avec le noeud au montage -- utile pour des mesures/abonnements ciblés
function ElementMesure() {
    function callbackRef(noeud) {
        if (noeud) {
            console.log("Monté, largeur :", noeud.getBoundingClientRect().width);
        }
    }
    return <div ref={callbackRef}>Contenu</div>;
}
jsx
// React 19 : le ref callback peut désormais RETOURNER une fonction de cleanup,
// exactement comme useEffect -- plus besoin de gérer démontage à la main
function ElementAvecObserver() {
    function callbackRef(noeud) {
        if (!noeud) return;

        const observateur = new ResizeObserver((entrees) => {
            console.log("Nouvelle taille :", entrees[0].contentRect);
        });
        observateur.observe(noeud);

        return () => observateur.disconnect(); // appelé automatiquement au démontage/changement de ref
    }

    return <div ref={callbackRef}>Contenu observé</div>;
}
jsx
// useImperativeHandle reste pertinent pour restreindre l'API exposée, même sans forwardRef
function ChampRiche({ ref, ...props }) {
    const inputRef = useRef(null);

    useImperativeHandle(ref, () => ({
        focus: () => inputRef.current.focus(),
        vider: () => { inputRef.current.value = ""; },
        // le parent n'a accès QU'à focus/vider, jamais au noeud DOM complet
    }));

    return <input ref={inputRef} {...props} />;
}
jsx
// Fusionner plusieurs refs sur un même élément : pattern utile avec des libs tierces (ex: Framer Motion)
function useRefsFusionnees(...refs) {
    return useCallback((noeud) => {
        refs.forEach((ref) => {
            if (typeof ref === "function") ref(noeud);
            else if (ref) ref.current = noeud;
        });
    }, refs);
}

function ChampAvecRefsMultiples({ ref: refExterne, ...props }) {
    const refInterne = useRef(null);
    const refFusionnee = useRefsFusionnees(refExterne, refInterne);
    return <input ref={refFusionnee} {...props} />;
}

Résumé

  • Depuis React 19, ref peut être reçu comme un prop normal sur un composant fonction : forwardRef devient optionnel pour le cas simple.
  • Un ref callback peut désormais retourner une fonction de nettoyage, appelée automatiquement au démontage ou au changement de noeud.
  • useImperativeHandle reste la façon de restreindre l'API exposée au parent, avec ou sans forwardRef.
  • Fusionner plusieurs refs sur un même élément nécessite un callback custom qui les applique toutes.

Exercices pratiques

1 disponible
1

Mission : moderniser le design system sans casser ChampRiche

Objectif : Simplifier des composants forwardRef devenus inutiles sous React 19, tout en gérant correctement le seul cas qui a réellement besoin de fusionner deux refs.

Contexte

L'équipe design system supprime forwardRef de Bouton.jsx et Carte.jsx, devenu inutile depuis React 19. Mais Champ.jsx (renommé ChampRiche) pose un problème différent : il doit à la fois exposer focus()/vider() au parent via useImperativeHandle, ET utiliser en interne un ResizeObserver sur le même noeud DOM pour logger ses changements de taille — ce qui suppose d'observer le noeud tout en respectant la ref externe reçue en prop.

Résoudre l’exercice →