Cours JavaScript et React · Module 5: useEffect et hooks personnalisés
Les références avec useRef
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 :
- Crée un champ de référence mutable qui survit pendant tout le cycle de vie du composant
- Ne provoque pas de nouveau rendu du composant lorsque sa valeur change
- 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 :
- Nous créons une référence
thrusterControlRefà l'aide deuseRef(null) - Nous affectons cette référence à l'élément
inputvia l'attributref - 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 :
| Aspect | useRef | useState |
|---|---|---|
| Provoque un nouveau rendu | Non | Oui |
| Conserve la valeur entre les rendus | Oui | Oui |
| Accès à la valeur | Via la propriété .current | Directement en lisant la variable d'état |
| Mise à jour de la valeur | Mutation directe ref.current = newValue | Via la fonction de mise à jour setState(newValue) |
| Quand l'utiliser ? | Pour les valeurs qui n'affectent pas le rendu | Pour 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
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.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.
Initialise les refs avec des valeurs initiales appropriées - par exemple
nullpour les références DOM ou des valeurs initiales pour les compteurs.Vérifie que ref.current existe avant de l'utiliser - surtout pour les références DOM qui peuvent être
nulllors du premier rendu.Utilise useCallback pour les fonctions créant des refs - si tu passes une fonction de référence, envisage de l'envelopper dans
useCallbackpour l'optimisation.É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 :
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)
- Accès à un élément DOM (
Intégration des références avec les effets et les callbacks
Manipulation directe de l'élément vidéo via son API
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. Que contient le tableau de dépendances dans useEffect ?
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