Chapitre 3 : useRef
Deux usages de useRef
useRef a deux usages principaux :
- Accéder à un élément DOM (input, div, etc.)
- Stocker une valeur qui persiste entre les rendus sans déclencher de re-rendu
Accéder au DOM
Exemple : Focus sur un input
- snippet.javascript
import { useRef } from 'react'; function SearchForm() { const inputRef = useRef(null); const handleClick = () => { inputRef.current.focus(); }; return ( <div> <input ref={inputRef} type="text" placeholder="Rechercher..." /> <button onClick={handleClick}>Mettre le focus</button> </div> ); }
Comment ça marche
useRef(null)crée un objet{ current: null }ref={inputRef}dit à React de mettre l'élément DOM dansinputRef.current- Après le rendu,
inputRef.currentcontient l'élément<input>
Exemple : Scroll vers un élément
- snippet.javascript
function ScrollToSection() { const sectionRef = useRef(null); const scrollToSection = () => { sectionRef.current.scrollIntoView({ behavior: 'smooth' }); }; return ( <div> <button onClick={scrollToSection}>Aller à la section</button> {/* ... beaucoup de contenu ... */} <section ref={sectionRef}> <h2>Section cible</h2> </section> </div> ); }
Exemple : Mesurer un élément
- snippet.javascript
function MeasuredBox() { const boxRef = useRef(null); const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); useEffect(() => { if (boxRef.current) { const { width, height } = boxRef.current.getBoundingClientRect(); setDimensions({ width, height }); } }, []); return ( <div> <div ref={boxRef} style={{ padding: 20, background: '#eee' }}> Contenu de la boîte </div> <p>Dimensions : {dimensions.width} x {dimensions.height}</p> </div> ); }
Stocker une valeur persistante
Différence avec useState
| Critère | useState | useRef |
|---|---|---|
| Persiste entre rendus | Oui | Oui |
| Déclenche un re-rendu | Oui | Non |
| Accès à la valeur | state | ref.current |
Exemple : Compter les rendus
- snippet.javascript
function RenderCounter() { const [count, setCount] = useState(0); const renderCount = useRef(0); // Incrémenté à chaque rendu, sans causer de re-rendu renderCount.current += 1; return ( <div> <p>Count : {count}</p> <p>Rendus : {renderCount.current}</p> <button onClick={() => setCount(c => c + 1)}>+1</button> </div> ); }
Exemple : Valeur précédente
- snippet.javascript
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; } function Counter() { const [count, setCount] = useState(0); const previousCount = usePrevious(count); return ( <div> <p>Actuel : {count}</p> <p>Précédent : {previousCount}</p> <button onClick={() => setCount(c => c + 1)}>+1</button> </div> ); }
Exemple : Éviter les re-exécutions de useEffect
- snippet.javascript
function Timer({ onTick }) { const onTickRef = useRef(onTick); // Met à jour la ref sans déclencher de re-rendu useEffect(() => { onTickRef.current = onTick; }, [onTick]); useEffect(() => { const interval = setInterval(() => { onTickRef.current(); // Utilise toujours la dernière version }, 1000); return () => clearInterval(interval); }, []); // Pas besoin de onTick dans les dépendances }
Cas d'usage avec formulaires non contrôlés
Parfois, il est plus simple de lire la valeur du DOM directement :
- snippet.javascript
function UncontrolledForm() { const nameRef = useRef(null); const emailRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); console.log({ name: nameRef.current.value, email: emailRef.current.value }); }; return ( <form onSubmit={handleSubmit}> <input ref={nameRef} type="text" placeholder="Nom" /> <input ref={emailRef} type="email" placeholder="Email" /> <button type="submit">Envoyer</button> </form> ); }
Note : les formulaires contrôlés (avec useState) sont généralement préférés car ils permettent la validation en temps réel.
Attention : ne pas lire/écrire pendant le rendu
- snippet.javascript
// INCORRECT function BadComponent() { const ref = useRef(0); ref.current += 1; // Modification pendant le rendu return <div>{ref.current}</div>; } // CORRECT - modifier dans useEffect ou gestionnaire d'événement function GoodComponent() { const ref = useRef(0); useEffect(() => { ref.current += 1; // OK dans useEffect }); const handleClick = () => { ref.current += 1; // OK dans gestionnaire }; return <div>...</div>; }
Exercices
Exercice 1 : Auto-focus au montage
Créer un formulaire de login qui met automatiquement le focus sur le champ email au chargement.
Solution :
- snippet.javascript
function LoginForm() { const emailRef = useRef(null); useEffect(() => { emailRef.current.focus(); }, []); return ( <form> <input ref={emailRef} type="email" placeholder="Email" /> <input type="password" placeholder="Mot de passe" /> <button type="submit">Connexion</button> </form> ); }
Exercice 2 : Chronomètre avec pause
Créer un chronomètre avec boutons Start/Pause/Reset.
Solution :
- snippet.javascript
function Stopwatch() { const [time, setTime] = useState(0); const [isRunning, setIsRunning] = useState(false); const intervalRef = useRef(null); useEffect(() => { if (isRunning) { intervalRef.current = setInterval(() => { setTime(t => t + 1); }, 1000); } return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, [isRunning]); const start = () => setIsRunning(true); const pause = () => setIsRunning(false); const reset = () => { setIsRunning(false); setTime(0); }; return ( <div> <p>{time} secondes</p> <button onClick={start} disabled={isRunning}>Start</button> <button onClick={pause} disabled={!isRunning}>Pause</button> <button onClick={reset}>Reset</button> </div> ); }
Points clés à retenir
- useRef pour le DOM :
ref={myRef}puismyRef.current - useRef pour persister : stocke une valeur sans re-rendu
- Ne pas modifier pendant le rendu : seulement dans useEffect ou événements
- Valeur dans
.current:ref.currentet nonref
← Chapitre précédent | Retour au module | Chapitre suivant : useContext →