Cours JavaScript et React · Module 5: useEffect et hooks personnalisés

Les références avec useRef

14 min de lecture
Dans cette leçon8

Dans notre voyage cosmique à travers React, nous devons parfois conserver certaines données ou des références vers des éléments du DOM qui persistent entre les rendus mais ne provoquent pas de nouveau rendu du composant. Le hook useRef est comme un compartiment de rangement sur un vaisseau spatial - il te permet de stocker des choses importantes qui ne changent pas à chaque manœuvre du vaisseau.

Qu'est-ce que useRef ?

Le hook useRef est l'un des hooks intégrés de React qui :

  1. Crée un champ de référence mutable qui survit pendant tout le cycle de vie du composant
  2. Ne provoque pas de nouveau rendu du composant lorsque sa valeur change
  3. Peut être utilisé pour stocker des références vers des éléments du DOM ou n'importe quelle autre valeur

Syntaxe de base :

1const refContainer = useRef(initialValue);

L'objet retourné refContainer possède une propriété .current qui est initialisée avec la valeur initiale (initialValue). Cette valeur peut être modifiée sans déclencher de nouveau rendu.

Principaux cas d'usage de useRef

1. Accéder aux éléments du DOM

L'une des utilisations les plus populaires de useRef est d'obtenir un accès direct aux nœuds du DOM :

1function SpaceshipControls() {
2  // Création de la référence
3  const thrusterControlRef = useRef(null);
4
5  // Fonction utilisant la référence vers l'élément DOM
6  const focusThrusterControl = () => {
7    // Accéder à l'élément DOM via la propriété .current
8    thrusterControlRef.current.focus();
9  };
10
11  return (
12    <div className="control-panel">
13      <h2>Panneau de contrôle des propulseurs</h2>
14
15      {/* Affectation de la référence à l'élément input */}
16      <input
17        ref={thrusterControlRef}
18        type="range"
19        min="0"
20        max="100"
21        className="thruster-slider"
22      />
23
24      <button onClick={focusThrusterControl}>
25        Activer le contrôle de la poussée
26      </button>
27    </div>
28  );
29}

Dans cet exemple :

  1. Nous créons une référence thrusterControlRef à l'aide de useRef(null)
  2. Nous affectons cette référence à l'élément input via l'attribut ref
  3. Nous utilisons la référence pour appeler la méthode focus() sur l'élément DOM

2. Stocker des valeurs mutables entre les rendus

useRef peut aussi être utilisé pour stocker des valeurs qui :

  • Ne doivent pas provoquer de nouveau rendu lorsqu'elles changent
  • Doivent rester stables pendant tout le cycle de vie du composant
1function MissionTimer() {
2  const [seconds, setSeconds] = useState(0);
3  const timerIdRef = useRef(null);
4
5  // Fonction pour démarrer la minuterie
6  const startTimer = () => {
7    if (timerIdRef.current !== null) return; // Éviter les minuteries en double
8
9    timerIdRef.current = setInterval(() => {
10      setSeconds(s => s + 1);
11    }, 1000);
12  };
13
14  // Fonction pour arrêter la minuterie
15  const stopTimer = () => {
16    clearInterval(timerIdRef.current);
17    timerIdRef.current = null;
18  };
19
20  // Nettoyer l'intervalle au démontage
21  useEffect(() => {
22    return () => {
23      if (timerIdRef.current !== null) {
24        clearInterval(timerIdRef.current);
25      }
26    };
27  }, []);
28
29  return (
30    <div className="mission-timer">
31      <p>Temps de mission : {seconds} secondes</p>
32      <button onClick={startTimer}>Démarrer</button>
33      <button onClick={stopTimer}>Arrêter</button>
34    </div>
35  );
36}

Dans cet exemple, nous utilisons useRef pour stocker l'ID de l'intervalle. C'est un cas d'usage idéal car :

  • Nous n'avons pas besoin d'un nouveau rendu lorsque l'ID de l'intervalle change
  • Nous devons conserver l'ID entre les rendus pour pouvoir arrêter l'intervalle
  • Cette valeur n'est pas utilisée directement dans le JSX rendu

3. Suivre les valeurs précédentes

Une utilisation intéressante de useRef est le suivi des valeurs précédentes de props ou d'état :

1function usePrevious(value) {
2  const ref = useRef();
3
4  // Mise à jour de ref.current après le rendu
5  useEffect(() => {
6    ref.current = value;
7  }, [value]);
8
9  // Retourne la valeur précédente (undefined lors du premier rendu)
10  return ref.current;
11}
12
13function AsteroidTracker({ asteroidId }) {
14  const [asteroidData, setAsteroidData] = useState(null);
15  // Suivre l'ID précédent
16  const previousAsteroidId = usePrevious(asteroidId);
17
18  // Effet qui journalise le changement de l'astéroïde observé
19  useEffect(() => {
20    if (previousAsteroidId !== asteroidId) {
21      console.log(`Le suivi est passé de l'astéroïde ${previousAsteroidId} à ${asteroidId}`);
22      // Ici nous pourrions récupérer de nouvelles données sur l'astéroïde
23    }
24  }, [asteroidId, previousAsteroidId]);
25
26  // Rendu...
27}

Le hook usePrevious utilise useRef pour stocker la valeur précédente. La référence est mise à jour dans useEffect, qui s'exécute après le rendu, donc la valeur stockée dans la ref est toujours un cycle de rendu « en retard » par rapport à la valeur actuelle.

4. Éviter les effets inutiles

useRef peut aider à l'optimisation en évitant les effets inutiles :

1function TelemetryMonitor({ shipId }) {
2  const [data, setData] = useState(null);
3  const [isConnected, setIsConnected] = useState(false);
4
5  // Stocker l'ID pour éviter les changements de connexion inutiles
6  const lastShipIdRef = useRef(shipId);
7
8  useEffect(() => {
9    // Vérifier si l'ID du vaisseau a réellement changé
10    if (lastShipIdRef.current === shipId && isConnected) {
11      console.log('Le vaisseau n\'a pas changé, on saute la reconnexion');
12      return;
13    }
14
15    // Mise à jour de la ref avec le nouvel ID
16    lastShipIdRef.current = shipId;
17
18    // Établir la connexion...
19    console.log(`Connexion à la télémétrie du vaisseau ${shipId}...`);
20
21    const connection = telemetryService.connect(shipId);
22    setIsConnected(true);
23
24    connection.onData(newData => {
25      setData(newData);
26    });
27
28    return () => {
29      console.log(`Déconnexion de la télémétrie du vaisseau ${shipId}...`);
30      connection.disconnect();
31      setIsConnected(false);
32    };
33  }, [shipId]);
34
35  // Rendu...
36}

useRef vs useState - lequel utiliser ?

La différence entre useRef et useState est essentielle à comprendre :

AspectuseRefuseState
Provoque un nouveau renduNonOui
Conserve la valeur entre les rendusOuiOui
Accès à la valeurVia la propriété .currentDirectement en lisant la variable d'état
Mise à jour de la valeurMutation directe ref.current = newValueVia la fonction de mise à jour setState(newValue)
Quand l'utiliser ?Pour les valeurs qui n'affectent pas le renduPour les valeurs qui doivent déclencher un nouveau rendu

Quand utiliser useRef :

  • Accéder aux éléments du DOM
  • Stocker les ID de minuteries et d'intervalles
  • Stocker les valeurs précédentes
  • Valeurs qui changent souvent mais n'affectent pas l'UI
  • Stocker des instances de classes et des objets qui ne devraient pas être recréés à chaque rendu

Quand utiliser useState :

  • Données qui affectent ce que l'utilisateur voit
  • Valeurs dont les changements doivent provoquer des mises à jour de l'UI
  • État qui contrôle le comportement ou l'apparence de l'interface utilisateur

Modèles avancés de useRef

1. Callback Refs

Parfois, nous avons besoin de plus de contrôle sur le moment où une ref est affectée. Dans ce cas, nous pouvons utiliser une callback ref :

1function SpectrumAnalyzer() {
2  const [frequency, setFrequency] = useState(440);
3  let canvasRef = useRef(null);
4
5  // Fonction de callback pour la ref, appelée lorsque la ref est affectée ou détachée
6  const setCanvasRef = useCallback(element => {
7    // element sera null pendant le démontage
8    if (element) {
9      canvasRef.current = element;
10      // Nous pouvons maintenant effectuer l'initialisation après l'affectation de la référence
11      initializeCanvas(element, frequency);
12    } else {
13      // Nettoyage des ressources au démontage
14      if (canvasRef.current) {
15        cleanupCanvas(canvasRef.current);
16      }
17      canvasRef.current = null;
18    }
19  }, [frequency]); // Dépendance sur frequency
20
21  // Fonction initialisant le canvas
22  const initializeCanvas = (canvas, freq) => {
23    console.log(`Initialisation de l'analyseur pour la fréquence ${freq} Hz`);
24    const ctx = canvas.getContext('2d');
25    // Dessin...
26  };
27
28  return (
29    <div>
30      <canvas
31        ref={setCanvasRef}
32        width="600"
33        height="300"
34      />
35      <input
36        type="range"
37        min="20"
38        max="20000"
39        value={frequency}
40        onChange={e => setFrequency(Number(e.target.value))}
41      />
42      <div>Fréquence : {frequency} Hz</div>
43    </div>
44  );
45}

La callback ref nous donne plus de contrôle car elle est appelée à la fois lors de l'affectation et du retrait de la référence.

2. Combiner useRef avec forwardRef

Parfois, nous voulons passer une référence à un composant enfant. Nous pouvons le faire avec forwardRef :

1// Composant enfant
2const NavigationControl = forwardRef((props, ref) => {
3  return (
4    <div className="nav-control">
5      <label>{props.label}</label>
6      <input
7        ref={ref}
8        type="text"
9        className="nav-input"
10        value={props.value}
11        onChange={props.onChange}
12      />
13    </div>
14  );
15});
16
17// Composant parent
18function NavigationPanel() {
19  const destinationInputRef = useRef(null);
20  const [destination, setDestination] = useState('');
21
22  const focusDestination = () => {
23    // Nous pouvons accéder à l'input interne grâce à forwardRef
24    destinationInputRef.current.focus();
25  };
26
27  return (
28    <div className="nav-panel">
29      <h2>Panneau de navigation</h2>
30
31      <NavigationControl
32        ref={destinationInputRef}
33        label="Destination :"
34        value={destination}
35        onChange={e => setDestination(e.target.value)}
36      />
37
38      <button onClick={focusDestination}>
39        Définir la destination
40      </button>
41    </div>
42  );
43}

forwardRef permet à un composant de recevoir une référence de son parent et de la passer à un élément spécifique à l'intérieur du composant.

3. Stocker plusieurs références

Parfois, nous devons suivre plusieurs éléments, par exemple une liste d'éléments :

1function StarMap({ stars }) {
2  // Stockage d'une map de références vers les étoiles
3  const starRefs = useRef({});
4
5  // Fonction pour faire défiler jusqu'à une étoile spécifique
6  const scrollToStar = (starId) => {
7    if (starRefs.current[starId]) {
8      starRefs.current[starId].scrollIntoView({
9        behavior: 'smooth',
10        block: 'center'
11      });
12    }
13  };
14
15  return (
16    <div className="star-map-container">
17      <div className="star-list">
18        {stars.map(star => (
19          <div
20            key={star.id}
21            // Nous sauvegardons la référence vers l'élément dans notre map
22            ref={el => starRefs.current[star.id] = el}
23            className="star-item"
24          >
25            <h3>{star.name}</h3>
26            <p>Type : {star.type}</p>
27            <p>Distance : {star.distance} années-lumière</p>
28          </div>
29        ))}
30      </div>
31
32      <div className="star-navigator">
33        <h3>Navigation rapide</h3>
34        {stars.map(star => (
35          <button
36            key={star.id}
37            onClick={() => scrollToStar(star.id)}
38          >
39            {star.name}
40          </button>
41        ))}
42      </div>
43    </div>
44  );
45}

Dans cet exemple, nous stockons une map de références où la clé est l'id de l'étoile et la valeur est une référence vers l'élément DOM correspondant.

Pièges courants lors de l'utilisation de useRef

1. Tenter de faire le rendu directement depuis ref.current

Comme changer ref.current ne provoque pas de nouveau rendu, tenter d'utiliser cette valeur directement dans le JSX peut entraîner des incohérences :

1// MAUVAIS : changer ref.current ne mettra pas à jour l'interface
2function BadCounter() {
3  const countRef = useRef(0);
4
5  const increment = () => {
6    // Ce changement ne provoquera pas de nouveau rendu
7    countRef.current += 1;
8    console.log(`Compteur : ${countRef.current}`); // Sera journalisé correctement
9  };
10
11  return (
12    <div>
13      <p>Compteur : {countRef.current}</p> {/* Ne se mettra pas à jour après le clic */}
14      <button onClick={increment}>Incrémenter</button>
15    </div>
16  );
17}
18
19// BON : utilise useState pour les valeurs qui affectent le rendu
20function GoodCounter() {
21  const [count, setCount] = useState(0);
22
23  const increment = () => {
24    setCount(c => c + 1);
25  };
26
27  return (
28    <div>
29      <p>Compteur : {count}</p> {/* Se mettra à jour après le clic */}
30      <button onClick={increment}>Incrémenter</button>
31    </div>
32  );
33}

2. Oublier .current

Une erreur courante est d'oublier d'accéder à la valeur via .current :

1// MAUVAIS : .current manquant
2function MissionLog({ onAddLog }) {
3  const logInputRef = useRef(null);
4
5  const handleAddLog = () => {
6    // Erreur : nous avons oublié .current
7    const logText = logInputRef.value;
8    onAddLog(logText);
9  };
10
11  return (
12    <div>
13      <input ref={logInputRef} type="text" />
14      <button onClick={handleAddLog}>Ajouter une entrée</button>
15    </div>
16  );
17}
18
19// BON : nous utilisons .current
20function MissionLog({ onAddLog }) {
21  const logInputRef = useRef(null);
22
23  const handleAddLog = () => {
24    // Utilisation correcte de .current
25    const logText = logInputRef.current.value;
26    onAddLog(logText);
27    logInputRef.current.value = '';
28  };
29
30  return (
31    <div>
32      <input ref={logInputRef} type="text" />
33      <button onClick={handleAddLog}>Ajouter une entrée</button>
34    </div>
35  );
36}

3. Mauvaise utilisation de ref pour contrôler le rendu

useRef n'est pas l'outil approprié pour contrôler le moment où un composant doit faire son rendu :

1// MAUVAIS : tenter d'utiliser une ref pour contrôler le rendu
2function BadRenderControl() {
3  const shouldUpdateRef = useRef(false);
4
5  const forceUpdate = () => {
6    shouldUpdateRef.current = true;
7    // Mécanisme pour forcer un nouveau rendu manquant
8  };
9
10  console.log('Rendu', shouldUpdateRef.current);
11
12  // Cela ne fonctionnera pas comme attendu
13  if (shouldUpdateRef.current) {
14    shouldUpdateRef.current = false;
15    // Quelques opérations...
16  }
17
18  return <button onClick={forceUpdate}>Rafraîchir</button>;
19}
20
21// BON : utiliser useState pour contrôler le rendu
22function GoodRenderControl() {
23  const [update, setUpdate] = useState(0);
24  const didUpdateRef = useRef(false);
25
26  const forceUpdate = () => {
27    setUpdate(u => u + 1); // Déclenche un nouveau rendu
28  };
29
30  console.log('Rendu', update);
31
32  useEffect(() => {
33    if (update > 0) {
34      didUpdateRef.current = true;
35      // Opérations après la mise à jour...
36    }
37  }, [update]);
38
39  return <button onClick={forceUpdate}>Rafraîchir</button>;
40}

Bonnes pratiques avec useRef

  1. Utilise useRef pour les valeurs qui n'affectent pas le rendu - si un changement de valeur doit provoquer une mise à jour de l'interface, utilise useState.

  2. Sois prudent avec la manipulation directe du DOM - la manipulation du DOM via des références devrait être limitée et bien réfléchie.

  3. Initialise les refs avec des valeurs initiales appropriées - par exemple null pour les références DOM ou des valeurs initiales pour les compteurs.

  4. Vérifie que ref.current existe avant de l'utiliser - surtout pour les références DOM qui peuvent être null lors du premier rendu.

  5. Utilise useCallback pour les fonctions créant des refs - si tu passes une fonction de référence, envisage de l'envelopper dans useCallback pour l'optimisation.

  6. Évite d'abuser des refs - bien que les refs soient utiles, trop s'y fier peut conduire à un code difficile à maintenir.

Exemple pratique : lecteur vidéo avancé pour les missions

Voici un exemple plus complexe qui illustre divers aspects de l'utilisation de useRef :

1import React, { useState, useRef, useEffect, useCallback } from 'react';
2
3function MissionVideoPlayer({ videoUrl, onProgress }) {
4  const [isPlaying, setIsPlaying] = useState(false);
5  const [duration, setDuration] = useState(0);
6  const [currentTime, setCurrentTime] = useState(0);
7  const [volume, setVolume] = useState(0.7);
8
9  // Références
10  const videoRef = useRef(null);          // Référence vers l'élément vidéo
11  const progressBarRef = useRef(null);    // Référence vers la barre de progression
12  const animationFrameRef = useRef(null); // ID requestAnimationFrame pour la mise à jour du temps
13  const isMountedRef = useRef(true);      // Indicateur signalant si le composant est monté
14  const lastUpdateTimeRef = useRef(0);    // Heure de la dernière mise à jour pour le throttling
15
16  // Fonction initialisant les métadonnées de la vidéo
17  const handleLoadedMetadata = () => {
18    if (videoRef.current) {
19      setDuration(videoRef.current.duration);
20      videoRef.current.volume = volume;
21    }
22  };
23
24  // Fonction mettant à jour la barre de progression
25  const updateProgressBar = useCallback(() => {
26    if (!videoRef.current || !isMountedRef.current) return;
27
28    const now = Date.now();
29    if (now - lastUpdateTimeRef.current > 200) { // Throttling à 5 mises à jour par seconde
30      setCurrentTime(videoRef.current.currentTime);
31      onProgress(videoRef.current.currentTime);
32      lastUpdateTimeRef.current = now;
33    }
34
35    // Continuer la mise à jour si la vidéo est en lecture
36    if (videoRef.current.paused) return;
37    animationFrameRef.current = requestAnimationFrame(updateProgressBar);
38  }, [onProgress]);
39
40  // Démarrer ou mettre en pause la lecture
41  const togglePlayPause = () => {
42    if (!videoRef.current) return;
43
44    if (videoRef.current.paused) {
45      videoRef.current.play();
46      setIsPlaying(true);
47      animationFrameRef.current = requestAnimationFrame(updateProgressBar);
48    } else {
49      videoRef.current.pause();
50      setIsPlaying(false);
51      if (animationFrameRef.current) {
52        cancelAnimationFrame(animationFrameRef.current);
53      }
54    }
55  };
56
57  // Changer la position de lecture après un clic sur la barre de progression
58  const handleProgressBarClick = (e) => {
59    if (!progressBarRef.current || !videoRef.current) return;
60
61    const rect = progressBarRef.current.getBoundingClientRect();
62    const position = (e.clientX - rect.left) / rect.width;
63    const newTime = duration * Math.max(0, Math.min(1, position));
64
65    videoRef.current.currentTime = newTime;
66    setCurrentTime(newTime);
67  };
68
69  // Changer le volume
70  const handleVolumeChange = (e) => {
71    const newVolume = parseFloat(e.target.value);
72    setVolume(newVolume);
73
74    if (videoRef.current) {
75      videoRef.current.volume = newVolume;
76    }
77  };
78
79  // Effet nettoyant animationFrame et définissant l'indicateur de montage
80  useEffect(() => {
81    return () => {
82      isMountedRef.current = false;
83      if (animationFrameRef.current) {
84        cancelAnimationFrame(animationFrameRef.current);
85      }
86    };
87  }, []);
88
89  // Formatage du temps (mm:ss)
90  const formatTime = (timeInSeconds) => {
91    const minutes = Math.floor(timeInSeconds / 60);
92    const seconds = Math.floor(timeInSeconds % 60);
93    return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
94  };
95
96  return (
97    <div className="mission-video-player">
98      <video
99        ref={videoRef}
100        src={videoUrl}
101        onLoadedMetadata={handleLoadedMetadata}
102        onEnded={() => setIsPlaying(false)}
103      />
104
105      <div className="controls">
106        <button onClick={togglePlayPause}>
107          {isPlaying ? 'Pause' : 'Lecture'}
108        </button>
109
110        <div className="time-display">
111          {formatTime(currentTime)} / {formatTime(duration)}
112        </div>
113
114        <div
115          ref={progressBarRef}
116          className="progress-bar"
117          onClick={handleProgressBarClick}
118        >
119          <div
120            className="progress-fill"
121            style={{ width: `${(currentTime / duration) * 100}%` }}
122          />
123        </div>
124
125        <div className="volume-control">
126          <label>Volume :</label>
127          <input
128            type="range"
129            min="0"
130            max="1"
131            step="0.05"
132            value={volume}
133            onChange={handleVolumeChange}
134          />
135        </div>
136      </div>
137    </div>
138  );
139}

Cet exemple illustre plusieurs aspects avancés du travail avec useRef :

  1. Plusieurs utilisations différentes des références :

    • Accès à un élément DOM (videoRef, progressBarRef)
    • Stockage de l'ID d'animation (animationFrameRef)
    • Indicateur de montage (isMountedRef)
    • Suivi de valeurs pour le throttling (lastUpdateTimeRef)
  2. Intégration des références avec les effets et les callbacks

  3. Manipulation directe de l'élément vidéo via son API

  4. Throttling des mises à jour d'état à l'aide de références temporelles

Résumé

Le hook useRef est un outil essentiel dans la boîte à outils de React, particulièrement utile pour :

  • Stocker des références vers des éléments du DOM
  • Conserver des valeurs entre les rendus sans déclencher de nouveau rendu
  • Suivre les valeurs précédentes
  • Stocker les ID de minuteries, d'intervalles et de requestAnimationFrame
  • Stocker des indicateurs et des valeurs qui n'affectent pas directement l'interface

La clé pour utiliser efficacement useRef est de comprendre quand l'utiliser à la place de useState. La différence fondamentale est que useRef ne provoque pas de nouveau rendu lorsque sa valeur change, ce qui en fait le choix idéal pour les opérations qui ne devraient pas affecter le cycle de rendu.

Dans notre voyage cosmique à travers React, useRef est comme un compartiment de rangement sur le vaisseau - un endroit où tu peux stocker les choses dont tu as besoin d'avoir sous la main, mais qui ne requièrent pas l'attention de tout l'équipage à chaque changement.

Code de cette leçon : App.jsx
1import React, { useState, useRef, useEffect } from 'react';
2
3// useRef pour stocker les références vers les éléments DOM
4function DOMReferenceDemo() {
5  const inputRef = useRef(null);
6  const canvasRef = useRef(null);
7
8  const focusInput = () => {
9    inputRef.current?.focus();
10  };
11
12  const drawOnCanvas = () => {
13    const canvas = canvasRef.current;
14    if (!canvas) return;
15
16    const ctx = canvas.getContext('2d');
17    ctx.clearRect(0, 0, canvas.width, canvas.height);
18
19    // Dessin des étoiles
20    for (let i = 0; i < 50; i++) {
21      const x = Math.random() * canvas.width;
22      const y = Math.random() * canvas.height;
23      const radius = Math.random() * 2;
24
25      ctx.beginPath();
26      ctx.arc(x, y, radius, 0, Math.PI * 2);
27      ctx.fillStyle = `rgba(255, 255, 255, ${Math.random()})`;
28      ctx.fill();
29    }
30
31    // Dessin de la planète
32    ctx.beginPath();
33    ctx.arc(canvas.width / 2, canvas.height / 2, 30, 0, Math.PI * 2);
34    ctx.fillStyle = '#61dafb';
35    ctx.fill();
36  };
37
38  return (
39    <div className="demo-card">
40      <h2>Références DOM</h2>
41
42      <div className="input-section">
43        <input ref={inputRef} type="text" placeholder="Clique sur le bouton ci-dessous" />
44        <button onClick={focusInput}>Focus sur l'input</button>
45      </div>
46
47      <div className="canvas-section">
48        <canvas ref={canvasRef} width={280} height={150} />
49        <button onClick={drawOnCanvas}>Dessiner le cosmos</button>
50      </div>
51    </div>
52  );
53}
54
55// useRef pour stocker des valeurs entre les rendus
56function PreviousValueDemo() {
57  const [count, setCount] = useState(0);
58  const prevCountRef = useRef(0);
59
60  useEffect(() => {
61    prevCountRef.current = count;
62  });
63
64  return (
65    <div className="demo-card">
66      <h2>Valeur précédente</h2>
67      <div className="values">
68        <div className="value-box">
69          <span className="label">Actuelle :</span>
70          <span className="number current">{count}</span>
71        </div>
72        <div className="value-box">
73          <span className="label">Précédente :</span>
74          <span className="number previous">{prevCountRef.current}</span>
75        </div>
76      </div>
77      <button onClick={() => setCount(c => c + 1)}>Augmenter</button>
78    </div>
79  );
80}
81
82// useRef pour mesurer les éléments
83function MeasurementDemo() {
84  const boxRef = useRef(null);
85  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
86  const [boxSize, setBoxSize] = useState(100);
87
88  const measureBox = () => {
89    if (boxRef.current) {
90      const rect = boxRef.current.getBoundingClientRect();
91      setDimensions({ width: rect.width, height: rect.height });
92    }
93  };
94
95  return (
96    <div className="demo-card">
97      <h2>Mesures des éléments</h2>
98      <div
99        ref={boxRef}
100        className="measure-box"
101        style={{ width: boxSize, height: boxSize }}
102      >
103        {boxSize}x{boxSize}
104      </div>
105      <div className="measurement-info">
106        Mesuré : {dimensions.width.toFixed(0)} x {dimensions.height.toFixed(0)}
107      </div>
108      <div className="controls">
109        <button onClick={() => setBoxSize(s => Math.max(50, s - 20))}></button>
110        <button onClick={measureBox}>Mesurer</button>
111        <button onClick={() => setBoxSize(s => Math.min(200, s + 20))}></button>
112      </div>
113    </div>
114  );
115}
116
117// useRef pour stocker les timeouts/intervalles
118function TimerRefDemo() {
119  const [time, setTime] = useState(0);
120  const [isRunning, setIsRunning] = useState(false);
121  const intervalRef = useRef(null);
122
123  const start = () => {
124    if (intervalRef.current) return;
125
126    setIsRunning(true);
127    intervalRef.current = setInterval(() => {
128      setTime(t => t + 1);
129    }, 100);
130  };
131
132  const stop = () => {
133    if (intervalRef.current) {
134      clearInterval(intervalRef.current);
135      intervalRef.current = null;
136    }
137    setIsRunning(false);
138  };
139
140  const reset = () => {
141    stop();
142    setTime(0);
143  };
144
145  // Cleanup lors du démontage
146  useEffect(() => {
147    return () => {
148      if (intervalRef.current) {
149        clearInterval(intervalRef.current);
150      }
151    };
152  }, []);
153
154  const formatTime = (t) => {
155    const seconds = Math.floor(t / 10);
156    const decimals = t % 10;
157    return `${seconds}.${decimals}s`;
158  };
159
160  return (
161    <div className="demo-card">
162      <h2>⏱Minuterie avec useRef</h2>
163      <div className="timer-display">{formatTime(time)}</div>
164      <div className="controls">
165        <button onClick={start} disabled={isRunning}>▶Démarrer</button>
166        <button onClick={stop} disabled={!isRunning}>⏸Pause</button>
167        <button onClick={reset}>Reset</button>
168      </div>
169      <p className="hint">ID de l'intervalle stocké dans useRef</p>
170    </div>
171  );
172}
173
174function App() {
175  return (
176    <div className="app">
177      <h1>useRef Hook</h1>
178      <p className="subtitle">Références et valeurs en dehors du cycle de rendu</p>
179      <div className="demos">
180        <DOMReferenceDemo />
181        <PreviousValueDemo />
182        <MeasurementDemo />
183        <TimerRefDemo />
184      </div>
185    </div>
186  );
187}
188
189export default App;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que contient le tableau de dépendances dans useEffect ?

  2. 2. Lequel des éléments suivants est un exemple d'effet de bord en React ?

Tâches pratiques dans le jeu

  • Tri horizontal

    Ordonne les éléments de la déclaration useRef, de gauche à droite :

  • Tri vertical

    Ordonne les étapes pour utiliser correctement async/await à l'intérieur de useEffect :

  • Clique dans l'ordre

    Ordonne les opérations asynchrones dans useEffect :

  • Éditeur de code

    Suivi de position par géolocalisation

Articles utiles