Avancé25 min de lecture

Refs & le DOM

Apprends à utiliser useRef pour accéder directement aux éléments DOM, stocker des valeurs mutables sans déclencher de re-rendus, et comprendre quand utiliser les refs plutôt que le state dans tes applications React.

Qu'est-ce que les Refs ?

Dans React, la façon standard d'interagir avec le DOM est via state et props — tu décris à quoi l'interface utilisateur devrait ressembler, et React gère les mises à jour du DOM. Mais parfois, tu dois sortir de ce modèle déclaratif et interagir directement avec un élément DOM.

Scénarios courants :

  • Donner le focus à un input après son montage ou après une action utilisateur
  • Mesurer la taille ou la position d'un élément
  • Intégrer des bibliothèques DOM tierces (lecteurs vidéo, bibliothèques de graphiques, etc.)
  • Déclencher des animations de manière impérative
  • Faire défiler jusqu'à un élément spécifique

Pour ces cas, React fournit le hook useRef. Une ref (abréviation de référence) est un moyen de conserver une référence à une valeur qui persiste entre les rendus mais ne déclenche pas de re-rendu lorsqu'elle change.

jsx
const myRef = React.useRef(initialValue);

useRef retourne un objet avec une seule propriété : current. L'objet lui-même est stable (même objet à chaque rendu), mais tu peux librement lire et écrire myRef.current.

javascript
const myRef = React.useRef(42);
console.log(myRef.current); // 42
myRef.current = 99;
console.log(myRef.current); // 99 — aucun re-rendu déclenché !

C'est fondamentalement différent de useState : modifier la valeur .current d'une ref ne provoque pas le re-rendu du composant.

useRef pour l'Accès au DOM

L'utilisation la plus courante de useRef est d'obtenir une référence à un élément DOM. Tu fais cela en passant la ref à l'attribut ref d'un élément :

jsx
function TextInput() {
  const inputRef = React.useRef(null);

  function handleClick() {
    // Accède à l'élément DOM réel
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="Tape ici..." />
      <button onClick={handleClick}>Focus l'input</button>
    </div>
  );
}

Voici ce qui se passe étape par étape :

  1. React.useRef(null) crée un objet ref : { current: null }
  2. L'attribut ref={inputRef} dit à React : "Après que cet élément <input> soit créé dans le DOM, définis inputRef.current sur ce nœud DOM."
  3. Quand le bouton est cliqué, inputRef.current est l'élément DOM <input> réel, donc appeler .focus() fonctionne comme en JavaScript vanilla.
  4. Quand le composant se démonte, React redéfinit inputRef.current sur null.

Mesurer des éléments

jsx
function MeasuredBox() {
  const boxRef = React.useRef(null);
  const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });

  React.useEffect(() => {
    if (boxRef.current) {
      const rect = boxRef.current.getBoundingClientRect();
      setDimensions({ width: rect.width, height: rect.height });
    }
  }, []);

  return (
    <div>
      <div ref={boxRef} style={{ width: '200px', height: '100px', background: '#eee' }}>
        Boîte mesurée
      </div>
      <p>Largeur : {dimensions.width}px, Hauteur : {dimensions.height}px</p>
    </div>
  );
}

Comme la ref est null pendant le premier rendu (l'élément DOM n'existe pas encore), nous utilisons useEffect pour lire les dimensions après que l'élément ait été affiché.

useRef pour les Valeurs Mutables

Au-delà de l'accès au DOM, useRef est utile pour stocker n'importe quelle valeur mutable qui devrait persister entre les rendus mais ne devrait pas déclencher de re-rendu lorsqu'elle change.

Stocker les valeurs précédentes

jsx
function Counter() {
  const [count, setCount] = React.useState(0);
  const prevCountRef = React.useRef(0);

  React.useEffect(() => {
    prevCountRef.current = count;
  });

  return (
    <p>
      Actuel : {count}, Précédent : {prevCountRef.current}
      <button onClick={() => setCount(c => c + 1)}>Incrémenter</button>
    </p>
  );
}

Stocker des IDs d'interval/timeout

Les refs sont parfaites pour conserver des IDs de minuterie qui doivent être effacés plus tard :

jsx
function Stopwatch() {
  const [seconds, setSeconds] = React.useState(0);
  const intervalRef = React.useRef(null);

  function start() {
    if (intervalRef.current) return; // déjà en cours
    intervalRef.current = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
  }

  function stop() {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
  }

  // Nettoyage au démontage
  React.useEffect(() => {
    return () => clearInterval(intervalRef.current);
  }, []);

  return (
    <div>
      <p>{seconds}s</p>
      <button onClick={start}>Démarrer</button>
      <button onClick={stop}>Arrêter</button>
    </div>
  );
}

Si nous utilisions useState pour l'ID d'interval, chaque démarrage/arrêt déclencherait un re-rendu inutile. Comme nous n'avons jamais besoin d'afficher l'ID d'interval, une ref est le bon choix.

La règle générale

Utilise useStateUtilise useRef
La valeur est affichée dans l'UILa valeur n'est pas affichée dans l'UI
La modifier devrait déclencher un re-renduLa modifier ne devrait pas déclencher de re-rendu
Exemples : compteur, input de formulaire, toggleExemples : ID de minuterie, valeur précédente, nœud DOM

Transfert de Refs et Gestion Impérative

Par défaut, les composants fonction n'exposent pas leurs nœuds DOM internes. Si un parent veut donner le focus à un input à l'intérieur d'un composant enfant, tu dois transférer la ref.

forwardRef

React.forwardRef crée un composant qui peut recevoir une ref de son parent et la transférer à un élément DOM :

jsx
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
  return (
    <input
      ref={ref}
      type="text"
      style={{ border: '2px solid blue', padding: '8px' }}
      {...props}
    />
  );
});

// Le parent peut maintenant référencer l'<input> interne
function Form() {
  const inputRef = React.useRef(null);
  return (
    <div>
      <FancyInput ref={inputRef} placeholder="Fancy!" />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </div>
  );
}

useImperativeHandle

Parfois tu veux exposer seulement des méthodes spécifiques au parent, plutôt que le nœud DOM entier. useImperativeHandle te permet de personnaliser la valeur de la ref :

jsx
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
  const inputRef = React.useRef(null);

  React.useImperativeHandle(ref, () => ({
    focus() {
      inputRef.current.focus();
    },
    clear() {
      inputRef.current.value = '';
    },
  }));

  return <input ref={inputRef} {...props} />;
});

// Le parent ne voit que focus() et clear(), pas le nœud DOM brut
function Form() {
  const inputRef = React.useRef(null);
  return (
    <div>
      <FancyInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
      <button onClick={() => inputRef.current.clear()}>Effacer</button>
    </div>
  );
}

Ce pattern suit le principe du moindre privilège — le parent obtient uniquement l'API dont il a besoin, pas un accès DOM direct.

Refs en Action

html
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
  function App() {
    const inputRef = React.useRef(null);
    const renderCount = React.useRef(0);
    const [text, setText] = React.useState("");

    renderCount.current += 1;

    return (
      <div>
        <h2>Démo useRef</h2>
        <input
          ref={inputRef}
          value={text}
          onChange={e => setText(e.target.value)}
          placeholder="Tape quelque chose..."
        />
        <button onClick={() => inputRef.current.focus()}>
          Focus l'Input
        </button>
        <button onClick={() => { inputRef.current.value = ""; setText(""); }}>
          Effacer
        </button>
        <p>Tu as tapé : {text}</p>
        <p>Nombre de rendus : {renderCount.current}</p>
      </div>
    );
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>

Que se passe-t-il quand tu changes la propriété .current d'une ref créée avec useRef ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.