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