Passer au contenu principal

Comprendre et utiliser le Hook useRef en React

Le hook useRef permet de créer une référence mutable qui persiste tout au long du cycle de vie du composant, sans déclencher de nouveau rendu lorsque sa valeur change.

Définition du hook useRef

  • Il prend en paramètre une valeur initiale qui est affectée à la propriété current.
  • Il retourne un objet contenant une unique propriété current.
const ref = useRef(null);

Caractéristiques du hook useRef

  • Modifier la valeur de ref.current n’entraîne pas un nouveau rendu du composant.
  • Le useRef est spécifique à chaque composant, donc si un composant est utilisé plusieurs fois, chaque instance aura sa propre référence distincte.

Principaux cas d'utilisation du hook useRef

1. Références DOM

L'une des utilisations principales de useRef est de créer des références aux éléments DOM, permettant ainsi d'accéder et d'interagir avec des éléments HTML directement.

import { useRef, useEffect } from "react";

export default function MyApp() {
  const ref = useRef(null);

  useEffect(() => {
    ref.current.focus();
  }, []);

  return <input type="text" ref={ref} />;
}

Lorsque l'élément HTML est supprimé, la valeur de ref.current est automatiquement réinitialisée à null.

2. Stockage de données persistantes entre les rendus

Le hook useRef est également utile pour stocker des informations qui doivent persister entre les rendus mais ne doivent pas déclencher de ré-rendu lorsqu'elles sont mises à jour. Cela inclut des variables comme un timer ou des informations de la précédente interaction utilisateur.

const previousValue = useRef(null);

useEffect(() => {
  previousValue.current = value; // Mémorise la valeur précédente sans provoquer de rendu
}, [value]);

Comment obtenir une référence à un composant enfant avec useRef ?

Vous pourriez instinctivement penser à passer une prop nommée ref à un composant enfant pour lui assigner une référence à un élément DOM, comme dans l'exemple suivant :

import { useRef, useState, useEffect } from "react";

function MyInput({ value, onChange, ref }) {
  return (
    <input value={value} ref={ref} onChange={(e) => onChange(e.target.value)} />
  );
}

export default function MyApp() {
  const ref = useRef(null);
  const [title, setTitle] = useState("");

  useEffect(() => {
    console.log(ref);
  }, []);

  return <MyInput value={title} ref={ref} onChange={setTitle} />;
}

Cependant, cette approche vous mènera à l'erreur suivante :

MyInput: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop.

En effet, dans React, la propriété ref n'est pas une prop classique. Elle ne peut pas être directement transmise comme une prop standard aux composants enfants.

Solution 1 : Utiliser un nom de prop différent pour la référence

Une solution consiste à renommer la propriété utilisée pour la référence, par exemple en la nommant inputRef au lieu de ref. Ainsi, vous évitez d'entrer en conflit avec le comportement réservé de ref.

import { useRef, useState, useEffect } from "react";

function MyInput({ value, onChange, inputRef }) {
  return (
    <input
      value={value}
      ref={inputRef}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

export default function MyApp() {
  const ref = useRef(null);
  const [title, setTitle] = useState("");

  useEffect(() => {
    console.log(ref);
  }, []);

  return <MyInput value={title} inputRef={ref} onChange={setTitle} />;
}

Solution 2 : Utiliser forwardRef pour transmettre la référence

Une autre solution plus appropriée consiste à utiliser la fonction forwardRef de React, qui permet explicitement de transférer une référence depuis un composant parent vers un composant enfant.

import { useRef, useState, useEffect, forwardRef } from "react";

const MyInput = forwardRef(function ({ value, onChange }, ref) {
  return (
    <input value={value} ref={ref} onChange={(e) => onChange(e.target.value)} />
  );
});

export default function MyApp() {
  const ref = useRef(null);
  const [title, setTitle] = useState("");

  useEffect(() => {
    console.log(ref);
  }, []);

  return <MyInput value={title} ref={ref} onChange={setTitle} />;
}

Dans React 19, les refs sont désormais transmises directement comme des props, éliminant ainsi le besoin d’utiliser forwardRef.

 

Personnaliser l’interface d’une ref avec useImperativeHandle

Dans l’exemple précédent, le composant parent obtient une référence directe sur le champ de saisie <input> grâce à forwardRef. Cependant, dans certains cas, on ne souhaite pas exposer toute la ref interne du composant enfant, mais uniquement certaines méthodes spécifiques.

C’est précisément le rôle du hook useImperativeHandle : il permet de choisir quelles méthodes ou propriétés un composant enfant rend accessibles à son parent via une ref.

Exemple : exposer une méthode focus() depuis un composant enfant

import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from "react";

const MyInput = forwardRef(function ({ value, onChange }, ref) {
  const inputRef = useRef(null);

  // Définir ce que la ref du parent pourra utiliser
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
  }));

  return (
    <input
      value={value}
      ref={inputRef}
      onChange={(e) => onChange(e.target.value)}
      placeholder="Tapez quelque chose..."
    />
  );
});

export default function MyApp() {
  const ref = useRef(null);
  const [title, setTitle] = useState("");

  useEffect(() => {
    console.log(ref); // ref.current contient { focus }
  }, []);

  return (
    <div>
      <MyInput value={title} ref={ref} onChange={setTitle} />
      <button onClick={() => ref.current.focus()}>Focus input</button>
    </div>
  );
}
  • Le composant MyInput utilise une ref interne inputRef pour accéder au champ <input>.

  • Avec useImperativeHandle, il expose au parent seulement la méthode focus().

  • Le parent MyApp peut maintenant appeler ref.current.focus() sans connaître la structure interne de MyInput.

Ainsi, la logique interne du composant enfant expose une API publique claire et maîtrisée.