Contactez-nous

`forwardRef` : Transmettre des refs à des composants enfants

Apprenez à utiliser React.forwardRef pour passer une ref d'un composant parent à un noeud DOM à l'intérieur d'un composant enfant encapsulé.

Le problème : L'encapsulation et les refs

Par défaut, les composants React encapsulent leur structure DOM interne. Un composant parent qui rend un composant enfant personnalisé n'a pas d'accès direct aux noeuds DOM rendus par cet enfant. C'est généralement une bonne chose pour la modularité et la maintenance.

Cependant, comme nous l'avons vu, il est parfois nécessaire pour un composant parent d'obtenir une référence (ref) à un noeud DOM spécifique *à l'intérieur* d'un composant enfant. Par exemple, pour mettre le focus sur un champ encapsulé dans un composant .

Le problème est que l'attribut ref, tout comme l'attribut key, est géré spécialement par React. Si vous essayez de passer une ref à un composant fonctionnel ou de classe personnalisé comme une prop ordinaire, cela ne fonctionnera pas comme prévu. React ne transmet pas automatiquement la prop `ref` aux enfants.

function Parent() {
  const fancyInputRef = useRef(null);

  useEffect(() => {
    // !!! CECI NE FONCTIONNERA PAS PAR DEFAUT !!!
    // fancyInputRef.current ne sera pas l'élément 
    // fancyInputRef.current?.focus(); 
  }, []);

  // On essaie de passer une ref à un composant personnalisé
  return ;
}

// Composant enfant standard (ne reçoit pas la ref)
function FancyInput({ label }) {
  // La prop 'ref' n'est pas accessible ici
  return (
    
{/* Le parent voudrait une ref sur cet input */}
); }

La solution : `React.forwardRef`

Pour résoudre ce problème, React fournit une fonction utilitaire spéciale appelée React.forwardRef. Elle agit comme un "pont" permettant à un composant de recevoir une ref de son parent et de la "transférer" (forward) vers un élément DOM spécifique qu'il rend.

React.forwardRef est une fonction d'ordre supérieur (Higher-Order Function) : vous lui passez votre fonction de rendu de composant en argument, et elle retourne un nouveau composant React capable de recevoir et de transmettre une ref.

La fonction de rendu que vous passez à `forwardRef` reçoit deux arguments :

  1. props : Les propriétés passées au composant, comme d'habitude.
  2. ref : La ref qui a été passée par le composant parent.

Votre travail consiste alors à prendre cet argument ref et à l'attacher à l'élément DOM désiré à l'intérieur de votre composant enfant.

Syntaxe et exemple

Reprenons notre exemple `FancyInput` et modifions-le pour utiliser `React.forwardRef` :

Composant enfant (`FancyInput.js`) utilisant `forwardRef` :
import React from 'react';

// 1. Envelopper la définition du composant dans React.forwardRef
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
  // 'props' contient les props normales (ex: label)
  // 'ref' contient la ref passée par le parent
  const { label, ...otherProps } = props;

  return (
    
{/* 2. Attacher la ref reçue à l'élément DOM interne désiré */}
); }); // (Optionnel mais recommandé) Donner un nom pour les DevTools FancyInput.displayName = 'FancyInput'; export default FancyInput;
Composant parent (`Parent.js`) utilisant l'enfant avec `forwardRef` :
import React, { useRef, useEffect } from 'react';
import FancyInput from './FancyInput';

function Parent() {
  // Crée la ref comme d'habitude
  const fancyInputRef = useRef(null);

  useEffect(() => {
    // Maintenant, cela fonctionne !
    // fancyInputRef.current pointe vers l'élément  grâce à forwardRef
    if (fancyInputRef.current) {
      fancyInputRef.current.focus(); 
      console.log('Focus mis sur FancyInput !');
    }
  }, []);

  // Passe la ref au composant enfant (qui utilise forwardRef)
  return (
    
  );
}

export default Parent;

Grâce à `React.forwardRef`, le composant `FancyInput` est maintenant capable d'accepter une `ref` du composant `Parent` et de la lier correctement à l'élément `input` interne. Le parent peut ainsi interagir directement avec cet `input`.

Cas d'usage courants de `forwardRef`

  • Création de composants réutilisables encapsulant des éléments DOM : C'est le cas d'usage le plus fréquent. Vous créez un composant stylisé ou avec une logique supplémentaire (comme `FancyInput`, `StyledButton`, etc.) mais vous voulez toujours permettre aux utilisateurs de ce composant d'obtenir une référence au noeud DOM sous-jacent si nécessaire.
  • Bibliothèques de composants : Les bibliothèques comme Material UI, Ant Design, etc., utilisent abondamment `forwardRef` pour que vous puissiez attacher des refs aux composants qu'elles fournissent.
  • Gestion du focus et accessibilité : Permettre à un composant parent de gérer le focus pour des raisons d'accessibilité ou de flux utilisateur.
  • Animations : Permettre le contrôle d'animations sur des éléments internes à un enfant.
  • Composants d'ordre supérieur (HOC) : Un HOC qui enveloppe un composant peut "casser" la transmission de ref. `forwardRef` peut être utilisé pour transmettre la ref à travers le HOC jusqu'au composant enveloppé.

Interaction avec `useImperativeHandle`

Il est important de noter que `forwardRef` est souvent utilisé conjointement avec un autre hook : useImperativeHandle. Alors que `forwardRef` permet de passer la ref, `useImperativeHandle` permet au composant enfant de personnaliser la valeur qui est exposée sur la propriété .current de la ref du parent.

Au lieu d'exposer l'intégralité du noeud DOM (ce qui peut briser l'encapsulation), l'enfant peut choisir d'exposer uniquement un objet avec quelques méthodes spécifiques (par exemple, un objet `{ focus: () => inputRef.current.focus(), clear: () => inputRef.current.value = '' }`). Cela donne plus de contrôle à l'enfant sur ce que le parent peut faire avec la ref. C'est une technique plus avancée pour une meilleure encapsulation lorsque vous exposez des fonctionnalités impératives.

Conclusion : Ouvrir une porte contrôlée vers le DOM enfant

`React.forwardRef` est un outil essentiel lorsque vous créez des composants réutilisables qui encapsulent des éléments DOM, mais que vous devez toujours permettre aux composants parents d'accéder à ces éléments DOM internes de manière contrôlée.

Il résout l'incapacité par défaut de passer l'attribut `ref` à des composants personnalisés en fournissant un mécanisme explicite pour recevoir et transférer cette ref. Son utilisation est fréquente dans les bibliothèques de composants et pour des cas d'usage spécifiques comme la gestion du focus ou l'intégration avec des API impératives.