Apprends à utiliser useRef pour accéder directement aux éléments DOM, stocker des valeurs mutables sans déclencher de re-rendus, et comprendre quand utiliser les refs plutôt que le state dans tes applications React.
Dans React, la façon standard d'interagir avec le DOM est via state et props — tu décris à quoi l'interface utilisateur devrait ressembler, et React gère les mises à jour du DOM. Mais parfois, tu dois sortir de ce modèle déclaratif et interagir directement avec un élément DOM.
Scénarios courants :
Pour ces cas, React fournit le hook useRef. Une ref (abréviation de référence) est un moyen de conserver une référence à une valeur qui persiste entre les rendus mais ne déclenche pas de re-rendu lorsqu'elle change.
const myRef = React.useRef(initialValue);useRef retourne un objet avec une seule propriété : current. L'objet lui-même est stable (même objet à chaque rendu), mais tu peux librement lire et écrire myRef.current.
const myRef = React.useRef(42);
console.log(myRef.current); // 42
myRef.current = 99;
console.log(myRef.current); // 99 — aucun re-rendu déclenché !C'est fondamentalement différent de useState : modifier la valeur .current d'une ref ne provoque pas le re-rendu du composant.
L'utilisation la plus courante de useRef est d'obtenir une référence à un élément DOM. Tu fais cela en passant la ref à l'attribut ref d'un élément :
function TextInput() {
const inputRef = React.useRef(null);
function handleClick() {
// Accède à l'élément DOM réel
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} type="text" placeholder="Tape ici..." />
<button onClick={handleClick}>Focus l'input</button>
</div>
);
}Voici ce qui se passe étape par étape :
React.useRef(null) crée un objet ref : { current: null }ref={inputRef} dit à React : "Après que cet élément <input> soit créé dans le DOM, définis inputRef.current sur ce nœud DOM."inputRef.current est l'élément DOM <input> réel, donc appeler .focus() fonctionne comme en JavaScript vanilla.inputRef.current sur null.function MeasuredBox() {
const boxRef = React.useRef(null);
const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
React.useEffect(() => {
if (boxRef.current) {
const rect = boxRef.current.getBoundingClientRect();
setDimensions({ width: rect.width, height: rect.height });
}
}, []);
return (
<div>
<div ref={boxRef} style={{ width: '200px', height: '100px', background: '#eee' }}>
Boîte mesurée
</div>
<p>Largeur : {dimensions.width}px, Hauteur : {dimensions.height}px</p>
</div>
);
}Comme la ref est null pendant le premier rendu (l'élément DOM n'existe pas encore), nous utilisons useEffect pour lire les dimensions après que l'élément ait été affiché.
Au-delà de l'accès au DOM, useRef est utile pour stocker n'importe quelle valeur mutable qui devrait persister entre les rendus mais ne devrait pas déclencher de re-rendu lorsqu'elle change.
function Counter() {
const [count, setCount] = React.useState(0);
const prevCountRef = React.useRef(0);
React.useEffect(() => {
prevCountRef.current = count;
});
return (
<p>
Actuel : {count}, Précédent : {prevCountRef.current}
<button onClick={() => setCount(c => c + 1)}>Incrémenter</button>
</p>
);
}Les refs sont parfaites pour conserver des IDs de minuterie qui doivent être effacés plus tard :
function Stopwatch() {
const [seconds, setSeconds] = React.useState(0);
const intervalRef = React.useRef(null);
function start() {
if (intervalRef.current) return; // déjà en cours
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
// Nettoyage au démontage
React.useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>{seconds}s</p>
<button onClick={start}>Démarrer</button>
<button onClick={stop}>Arrêter</button>
</div>
);
}Si nous utilisions useState pour l'ID d'interval, chaque démarrage/arrêt déclencherait un re-rendu inutile. Comme nous n'avons jamais besoin d'afficher l'ID d'interval, une ref est le bon choix.
Utilise useState | Utilise useRef |
|---|---|
| La valeur est affichée dans l'UI | La valeur n'est pas affichée dans l'UI |
| La modifier devrait déclencher un re-rendu | La modifier ne devrait pas déclencher de re-rendu |
| Exemples : compteur, input de formulaire, toggle | Exemples : ID de minuterie, valeur précédente, nœud DOM |
Par défaut, les composants fonction n'exposent pas leurs nœuds DOM internes. Si un parent veut donner le focus à un input à l'intérieur d'un composant enfant, tu dois transférer la ref.
React.forwardRef crée un composant qui peut recevoir une ref de son parent et la transférer à un élément DOM :
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
return (
<input
ref={ref}
type="text"
style={{ border: '2px solid blue', padding: '8px' }}
{...props}
/>
);
});
// Le parent peut maintenant référencer l'<input> interne
function Form() {
const inputRef = React.useRef(null);
return (
<div>
<FancyInput ref={inputRef} placeholder="Fancy!" />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
}Parfois tu veux exposer seulement des méthodes spécifiques au parent, plutôt que le nœud DOM entier. useImperativeHandle te permet de personnaliser la valeur de la ref :
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
clear() {
inputRef.current.value = '';
},
}));
return <input ref={inputRef} {...props} />;
});
// Le parent ne voit que focus() et clear(), pas le nœud DOM brut
function Form() {
const inputRef = React.useRef(null);
return (
<div>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => inputRef.current.clear()}>Effacer</button>
</div>
);
}Ce pattern suit le principe du moindre privilège — le parent obtient uniquement l'API dont il a besoin, pas un accès DOM direct.
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function App() {
const inputRef = React.useRef(null);
const renderCount = React.useRef(0);
const [text, setText] = React.useState("");
renderCount.current += 1;
return (
<div>
<h2>Démo useRef</h2>
<input
ref={inputRef}
value={text}
onChange={e => setText(e.target.value)}
placeholder="Tape quelque chose..."
/>
<button onClick={() => inputRef.current.focus()}>
Focus l'Input
</button>
<button onClick={() => { inputRef.current.value = ""; setText(""); }}>
Effacer
</button>
<p>Tu as tapé : {text}</p>
<p>Nombre de rendus : {renderCount.current}</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Que se passe-t-il quand tu changes la propriété .current d'une ref créée avec useRef ?