codelynx.dev
🇫🇷🇬🇧

Retour 30/09/2024

Comment utilisé useRef en React ? Guide complet

Écris par Melvyn Malherbe le 30/09/2024


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.

JSX
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.

Ref pour récupérer un élément du DOM

Je te propsoe de faire un petit détour pour voir l'utilisation de useRef avec la props ref pour récupérer un élément du DOM.

Chaque élément par défaut (div, span, etc...) possède une propriété ref qui permet de récupérer la référence de l'élément.

Cette proipriété ref peut prendre 2 type de paramptre :

  1. Un callback (une méthode) qui prends la référence en paramètre
  2. Un object et dans ce cas, par défaut il définit la propriété current comme la ref

Quand je dis ref dans le contexte d'un élément React, je parle de l'élément du DOM associé. React gère la majorité des choses pour toi, mais parfois on souhaites réucpérer l'élément du DOM pour pouvoir le manipuler.

Voici un exemple, tu peux voir que dans les deux cas il est possible de récupérer la référence de l'élément :

Quand on utilise useRef, on utilise la deuxième méthode, du moment que la props ref prends un object en paramètre, elle définit la valeur de la propriété current comme la ref.

Ensuite, useRef va stocker la valeur entre les render spécifiquement pour le composnats, on verra ensuite comment.

Maintenat parlons sérieux...

C'est quoi une ref ?

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

JS
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 :

JS
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 :

JSX
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 co nnu 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 :

JSX
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.

Le meilleur moyen d'apprendre React !

Rejoins par développeurs, cette formation reçoit une note de 4.7 / 5 🚀

Reçois 12 leçons premium pour maîtriser React et faire partie des meilleurs

BeginReact

Cours React gratuit

Accède à des exercices, des vidéos et bien plus sur React dans la formation "BeginReact" 👇