La face cachée du useRef - React Hooks

August 26, 202221 min read

banner url

useRef est un des hooks fondamental de React. Il permet de nombreuses choses et il est, à mon sens, pas assez utilisé. Dans cet article on va essayer de comprendre quelles sont les utilités du useRef et ses différences avec useState.

Documentation de useRef

D'après la documentation React le useRef permet de stocker une valeur qui n'est pas nécessaire pour le rendu.

Usage

const Component = () => {
  const ref = useRef(12);
  console.log(ref.current); // 12
};

useRef va retourner une ref object avec une unique propriété nommée current qui contient la valeur stockée. Dans le prochain render la ref sera toujours la même... il faut la modifier et utiliser la méthode current.current = value

On peut notamment l'utiliser pour récupérer la référence d'un élément du DOM. Dans cette article on ne va pas s'attarder sur cette utilisation. Mais tu peux en savoir plus dans cette vidéo YouTube.

Bref la documentation React l'explique bien mieux que moi donc je te propose de te le faire comprendre visuellement.

Mais pour comprendre useRef il faut comprendre :

C'est quoi une ref ?

Peux-tu deviner la valeur de value.current ici :

const value = { current: 0 };
const value2 = value;
const value3 = value2;

value3.current = 1;

Réponse 1

Visuellement ce code donne ça :

value{"current":0}
value2{"current":0}
value3{"current":0}
{"current":0}

ℹ️ En cliquant sur Next tu crées une nouvelle variable qui reprend la valeur précédente. En cliquant sur "Set current value" tu incrémentes de 1 current.

Tu vois que toutes les variables changent. Tu as en fait tu as changé la valeur de leur référence (carré orange), et elles partagent toutes la même référence. Elles sont donc toutes impactées par ce changement.

Et là, tu peux légitimement te demander :

Pourquoi { current: 0 } et pas juste 0 ?

Voici le même exemple qu'avant avec 0 :

value0
0
value20
0
value30
0

0 est un number, c'est une valeur primitive. J'ai envie de dire qu'une valeur primitive est sa propre référence. Quand tu la changes, tu modifies la référence.

Une référence, c'est un state qui ne change jamais.

Finalement, dans ton code React, on peut facilement les comparer :

const [state] = useState({ current: 0 });
// Égale
const ref = useRef(0);

state.current = 1; // pas de render
ref.current = 1; // pas de render

Par ailleurs c'est pour ça que push ne sert à rien si tu veux modifier l'état d'un tableau avec setState. Push ne modifie pas la ref ! C'est pour ça qu'il faut utiliser setState([...state, value]) car dans ce cas on recrée une ref.

Voici un exemple d'un state [1,2,3] avec un bouton qui rajoute 4 pour faire un tableau de [1,2,3,4] :

state[1,2,3]
[1,2,3]
[1,2,3]

On voit que quand on change le state, React crée une nouvelle référence et le state pointe sur cette nouvelle référence.

useRef vs useState vs variable

Pour finalement comprendre les différences et les subtilités de nos hooks favoris en React, voici un exemple :

let variable = 0;

const Component = () => {
  const [state, setState] = useState(0);
  const ref = useRef(0);

  const incrementState = () => {
    setState(state + 1);
  };

  const incrementRef = () => {
    ref.current += 1;
  };

  const incrementVariable = () => {
    variable += 1;
  };

  return (
    <div>
      <button onClick={incrementState}>Increment state ({state})</button>
      <button onClick={incrementRef}>Increment ref ({ref.current})</button>
      <button onClick={incrementVariable}>Increment variable ({variable})</button>
    </div>
  );
};

Voici le rendu pour le code ci-dessus. Amuse-toi à cliquer sur increment ref et increment variable plusieurs fois puis clique sur increment state.

ça donne quoi ? Quand tu cliques sur le useRef rien ne se passe, pareil pour la variable. Par contre, à l'instant où tu cliques sur le useState la valeur du button useRef et de la variable change !

Pourquoi ? Car un state provoquer un rerender qui va automatiquement mettre à jour le composant. Lorsque le composant va être rendu, il va prendre les valeurs de useRef et de la variable à jour.

Les rerenders sont visibles car le composant "bip" en vert

Il faut bien comprendre que la vue n'est update que quand le composant est mis à jour. Le useRef permet donc de stocker une valeur, mais elle n'influe pas le render.

Mais quel est l'avantage de useRef par rapport à une simple variable (comme dans l'exemple au-dessus du composant) ?

Voici un deuxième exemple, mais ce qui est intéressant, c'est que cette fois-ci, la variable et le useRef n'ont pas le même comportement.

Ce qui vient de se passer c'est que la variable n'est pas liée au composant. C'est une simple variable que tu pourrais déclarer dans un fichier script.js random.

La variable garde le même état même si c'est une instance différente.

Faire ce genre de chose est totalement déconseillé car ton composant devient impur. Les pure fonction sont des fonctions qui retournent toujours la même chose si on leur donne les mêmes paramètres. Utiliser des variables externes peut causer des bugs inattendus.

Quand se servir de useRef

useRef est parfait pour stocker des valeurs qui ne sont pas affichées dans la vue.

Comme tu l'as vu avec l'exemple des buttons, ce n'est pas très pratique d'avoir un useRef pour gérer un Counter. Normal, ce n'est pas fait pour.

Si tu veux afficher une valeur, comme la list précédente → tu utilises un useState.

Quelques exemples concrets :

useDebounce

C'est un hook connu qui permet d'appeler une fonction après un délai.

Dans le cadre d'une search bar tu n'as pas envie de fetch ton API avec la query à chaque lettre tapée. Avec le hook useDebouce tu vas fetch l'API quand l'utilisateur aura fini de taper depuis 1 seconde par exemple.

Tu peux tester, écris quelque chose et l'alert se fera 1 seconde après.

Le useRef me permet ici de stocker la valeur retournée par le time out. Ce qui me permet de clear dès que la fonction est rappelée. Pourquoi j'utilise un useRef ici ? Car je ne souhaite uniquement stocker la valeur, je ne l'affiche nulle part.

C'est possible de le faire avec useState, mais tu vas provoquer des render inutiles. Ce n'est pas la fin du monde, mais c'est comme utiliser une voiture pour faire 100 mètres.

Canvas

J'ai déjà fait un petit canva, où tu peux dessiner.

Voici le code pour ce canva :

const Canvas = () => {
  const canvas = useRef(null);
  const isDrawing = useRef(false);
  const prevMouse = useRef(null);

  const startDrawing = (event) => {
    isDrawing.current = true;
    prevMouse.current = getCoordinates(event, canvas.current);
  };

  const stopDrawing = () => {
    isDrawing.current = false;
    prevMouse.current = null;
  };

  const draw = (event) => {
    if (!isDrawing.current) return;
    const context = canvas.current?.getContext('2d');
    // ... draw line with context
    prevMouse.current = mouse;
  };

  return <canvas ref={canvas} /* ... all events listeners */ />;
};

Code complet

Dans cet exemple j'utilise canvas comme ref pour garder une référence sur le canva. Et j'utilise aussi les références isDrawing et prevMouse pour gérer la fonctionnalité de dessin.

En fait, c'est comme des variables dans mon code et les ref ici sont parfaites, car pour faire ce dessin j'interargis directement avec le DOM et je n'ai pas envie de faire travailler ReactDOM avec des render inutiles !

Conclusion

Dans cet article tu as compris le fonctionnement des références en JavaScript ainsi que le fonctionnement du hook useRef. Tu sais maintenant différencier useRef et useState et tu sais même quand et où utiliser une ref plutôt qu'un state.

Cette article à été créer pour la formation BeginReact.dev !

logo

BeginReact.dev

Arrête de douter...
Et viens dompter React 🦁 !

Regarde la vidéo masterclass


my profile picture

Écris par Melvyn Malherbe

J'ai aidé 200 personnes à apprendre React et Next.js. Je suis un créateur de contenu et un développeur web. Plus de 2'600 personnes reçoivent mes newsletter par email chaque semaine.

En apprendre plus à propos de moi