Contactez-nous

Stocker des valeurs mutables persistantes sans causer de re-render

Utilisez le hook useRef de React pour stocker et modifier des valeurs qui persistent entre les rendus sans déclencher de mise à jour de l'interface utilisateur.

Le besoin : persistance sans impact sur le rendu

Nous savons que `useState` et `useReducer` permettent de gérer l'état d'un composant. La caractéristique clé de ces hooks est que lorsque l'état est mis à jour via leur fonction de mise à jour (setState ou dispatch), React déclenche un re-rendu du composant pour refléter ce changement dans l'interface utilisateur.

Cependant, il existe des situations où l'on a besoin de conserver une information pendant toute la durée de vie du composant (elle doit persister entre les rendus), mais dont la modification ne doit pas provoquer de re-rendu. Il peut s'agir d'une valeur utilisée pour la logique interne, d'un identifiant, ou de toute autre donnée mutable qui n'a pas d'incidence directe sur ce qui est affiché à l'écran.

Utiliser `useState` pour de telles valeurs serait inefficace, car chaque modification entraînerait un re-rendu inutile. C'est précisément pour ce scénario que `useRef` offre une alternative élégante.

`useRef` : une référence mutable qui survit aux rendus

Lorsque vous appelez useRef(initialValue), React crée un objet simple avec une seule propriété : current. Cette propriété est initialisée avec la initialValue que vous avez fournie.

const maRef = useRef(0); // maRef est un objet: { current: 0 }
const autreRef = useRef(null); // maRef est { current: null }

La magie de `useRef` réside dans les points suivants :

  • Persistance : L'objet ref retourné par `useRef` est le même objet à travers tous les rendus du composant. Sa référence ne change pas.
  • Mutabilité : Vous pouvez modifier la valeur de la propriété .current directement, à tout moment : maRef.current = nouvelleValeur;.
  • Pas de re-rendu : Le point crucial est que la modification de maRef.current ne déclenche pas de re-rendu du composant. React ne surveille pas les changements de la propriété .current.

Pensez à une ref comme à une "boîte" ou une "variable d'instance" attachée à votre composant fonctionnel, qui vous permet de lire et d'écrire une valeur sans interférer avec le cycle de rendu de React.

Cas d'usage : quand utiliser `useRef` pour des données ?

Voici des situations typiques où stocker une valeur mutable dans une ref est approprié :

1. Stocker des identifiants de Timers/Intervalles :

Comme vu dans l'exemple du `StoppableTimer`, `useRef` est parfait pour garder l'ID retourné par `setTimeout` ou `setInterval` afin de pouvoir l'annuler plus tard (par exemple, dans un `useEffect` cleanup ou un gestionnaire d'événement) sans causer de re-rendu à chaque fois que le timer est démarré ou arrêté.

2. Suivre la valeur précédente d'une prop ou d'un état :

Parfois, vous avez besoin de comparer la valeur actuelle d'une prop ou d'un état avec sa valeur lors du rendu précédent. `useRef` permet de stocker l'ancienne valeur.

import React, { useState, useEffect, useRef } from 'react';

function PreviousValueDisplay({ value }) { // 'value' est une prop
  const previousValueRef = useRef();

  // S'exécute APRES chaque rendu
  useEffect(() => {
    // Met à jour la ref avec la valeur actuelle POUR le prochain rendu
    previousValueRef.current = value;
  }); // Pas de dépendances = s'exécute après chaque rendu

  const previousValue = previousValueRef.current;

  return (
    

Valeur actuelle : {value}

Valeur précédente : {previousValue === undefined ? 'N/A' : previousValue}

); } // Exemple d'utilisation function App() { const [count, setCount] = useState(0); return (
); } export default App;

Dans cet exemple, `previousValueRef.current` contient la valeur de `value` du rendu *précédent* car la mise à jour de la ref dans `useEffect` se produit *après* que le JSX du rendu actuel a été calculé.

3. Stocker des "variables d'instance" mutables :

Pour toute logique interne qui nécessite de conserver une information mutable entre les rendus sans affecter l'UI. Par exemple, un booléen pour savoir si une requête est déjà en cours, une référence à un objet de connexion WebSocket, etc.

function DataFetcher() {
  const isFetchingRef = useRef(false);

  const fetchData = () => {
    if (isFetchingRef.current) {
      console.log('Déjà en cours de fetch, annulation.');
      return; // Evite les fetches concurrents
    }
    isFetchingRef.current = true;
    console.log('Lancement du fetch...');
    fetch('/api/...')
      .then(/* ... */)
      .finally(() => {
        isFetchingRef.current = false; // Réinitialise le flag
        console.log('Fetch terminé.');
      });
  };
  // ...
}

Comparaison : `useRef` vs variable standard vs `useState`

Il est important de distinguer `useRef` pour les données des autres façons de gérer des valeurs :

  • Variable standard (déclarée avec `let` ou `const` dans le composant) : Elle est réinitialisée à chaque rendu. Elle ne persiste pas. Utile uniquement pour des calculs temporaires au sein d'un seul rendu.
  • `useState` : Elle persiste entre les rendus. Sa modification via la fonction setter déclenche un re-rendu. A utiliser lorsque le changement doit être reflété dans l'UI.
  • `useRef` (pour les données) : Elle persiste entre les rendus. Sa modification directe (`.current = ...`) ne déclenche PAS de re-rendu. A utiliser pour des valeurs mutables internes, non liées directement à l'affichage.

Conclusion : une mémoire privée pour la logique interne

Le hook `useRef` n'est pas seulement utile pour accéder au DOM. Sa capacité à fournir un conteneur mutable persistant, dont les modifications n'entraînent pas de re-rendus, en fait un outil précieux pour gérer certains aspects de la logique interne d'un composant fonctionnel.

C'est l'équivalent le plus proche d'une "variable d'instance" dans le monde des composants fonctionnels. Lorsque vous avez besoin de suivre une information changeante au fil du temps sans que cela n'affecte directement l'interface utilisateur, `useRef` est souvent la solution la plus appropriée et la plus performante par rapport à l'utilisation de `useState`.