DOM, valeurs mutables et composants réutilisables
Utilisez les flèches, cliquez ou glissez pour naviguer
1. Comprendre useRef
Ses deux usages : DOM et valeur mutable
2. Accéder au DOM
Focus, scroll, mesure d'éléments
3. Contrôlé vs Non-contrôlé
Quand utiliser quelle approche
4. forwardRef
Exposer une ref depuis un composant
5. Valeurs mutables sans re-render
Stocker des données qui ne déclenchent pas de render
Rappel : useState déclenche un re-render
Mais parfois on veut stocker une valeur SANS re-render
Introduction Ă useRef
Une "boîte" qui persiste entre les renders
Demo live : DOM access
Focus un input, scroller, mesurer un élément
Contrôlé vs Non-contrôlé
React possède la valeur vs DOM possède la valeur
forwardRef
Exposer une ref depuis un composant enfant
Modifier le state = re-render
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
// Le composant se re-render!
};
Mais parfois, on veut stocker une valeur SANS déclencher de re-render...
Comment accéder à un élément DOM ?
Focus automatiquement un input
Scroller vers un élément
Mesurer la taille d'un élément
Intégrer une librairie tierce (D3, animation...)
En vanilla JS : document.getElementById()
Mais en React ?
Une "boîte" qui persiste entre les renders
2 usages principaux :
Accéder au DOM · Stocker une valeur mutable
import { useRef } from 'react';
const myRef = useRef(initialValue);
myRef.current = la valeur stockée
Persistant
La valeur survit aux re-renders
Sans re-render
Modifier .current ne déclenche PAS de render
useState
Pour l'UI
Déclenche un re-render
Valeur affichée dans le JSX
React "possède" la donnée
const [count, setCount] = useState(0);
useRef
Pour les "coulisses"
PAS de re-render
Valeur "cachée" (pas dans JSX)
Tu gères manuellement .current
const ref = useRef(initialValue);
Connecter une ref à un élément JSX
Utiliser l'attribut ref
function SearchInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<input
ref={inputRef}
type="text"
placeholder="Rechercher..."
/>
);
}
L'input reçoit le focus automatiquement au montage
Créer la ref
const inputRef = useRef(null);
L'attacher à l'élément
<input ref={inputRef} />
React fait le lien
inputRef.current = l'élément DOM réel
Au premier render, ref.current est null (le DOM n'existe pas encore)
function ChatMessages() {
const bottomRef = useRef(null);
const [messages, setMessages] = useState([]);
useEffect(() => {
bottomRef.current?.scrollIntoView();
}, [messages]);
return (
<div>
{messages.map(msg => <p key={msg.id}>{msg.text}</p>)}
<div ref={bottomRef} />
</div>
);
}
Ă€ chaque nouveau message, on scroll automatiquement vers le bas
function BoxSize() {
const boxRef = useRef(null);
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const { width, height } = boxRef.current.getBoundingClientRect();
setSize({ width, height });
}, []);
return (
<div ref={boxRef} className="box">
Taille: {size.width} x {size.height}px
</div>
);
}
getBoundingClientRect() donne la position et taille réelles
Stocker une valeur sans re-render
Comme une variable "cachée" qui persiste
On veut compter les clics SANS afficher le compte
function ClickTracker() {
const clickCount = useRef(0);
const handleClick = () => {
clickCount.current += 1;
// PAS de re-render!
console.log(`Clics: ${clickCount.current}`);
};
return <button onClick={handleClick}>Cliquez!</button>;
}
Si la valeur doit être affichée, utilisez useState!
Le timer ID change mais n'a pas besoin d'être affiché
function Stopwatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
setTime(t => t + 1);
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
return <div>{time}s <button onClick={stop}>Stop</button></div>;
}
intervalRef.current persiste entre les renders
Bons cas
Timer ID, interval ID
Cache de données
Compteur "caché"
Référence à la valeur précédente
Mauvais cas
Valeur affichée dans JSX
Données qui déclenchent un calcul
État de l'interface (loading, error)
Règle d'or : Si la valeur doit être affichée, c'est du useState!
Qui possède la valeur de l'input ?
Contrôlé
React possède la valeur
Non-contrôlé
Le DOM possède la valeur
React "contrĂ´le" la valeur via useState
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
Avantages
Validation instantanée, transformation, désactiver
Inconvénients
Re-render Ă chaque frappe
Le DOM gère la valeur, on lit via ref
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} defaultValue="initial" />
<button type="submit">Envoyer</button>
</form>
);
}
Utiliser defaultValue pour la valeur initiale (pas value)
Contrôlé
React = source de vérité
Validation en temps réel
Transformation (uppercase, format)
Désactiver conditionnellement
Valeur dérivée d'un autre state
<input value={state} ... />
Non-contrôlé
DOM = source de vérité
Form simple, validation au submit
Intégration avec librairies tierces
Performance (pas de re-render)
File inputs
<input ref={ref} defaultValue="..." />
Comment passer une ref Ă un composant enfant ?
Un composant personnalisé ne peut pas recevoir de ref par défaut
On veut focus un CustomInput
function MyForm() {
const inputRef = useRef(null);
return (
<CustomInput
ref={inputRef}
// Ne marche pas!placeholder="..."
/>
);
}
React ne passe pas automatiquement ref aux composants personnalisés
Envelopper le composant avec forwardRef
const CustomInput = forwardRef((props, ref) => {
return (
<input
ref={ref}
// On passe la ref au DOM{...props}
className="custom-input"
/>
);
});
Maintenant le parent peut faire inputRef.current.focus()
const MyComponent = forwardRef((props, ref) => {
// props = les props normales
// ref = la ref passée par le parent
return <div ref={ref}>{props.children}</div>;
});
props
Les props habituelles
ref
Le 2ème argument spécial
const LabeledInput = forwardRef(({ label, ...props }, ref) => {
return (
<div className="form-group">
<label>{label}</label>
<input ref={ref} {...props} />
</div>
);
});
// Utilisation :
function Form() {
const emailRef = useRef(null);
return (
<LabeledInput
ref={emailRef}
label="Email"
type="email"
/>
);
}
Le parent peut accéder à l'input via emailRef.current
Erreurs à éviter
4 erreurs fréquentes
Si la valeur doit être affichée, c'est du useState!
Mauvais
const count = useRef(0);
return <p>{count.current}</p>;
// Jamais mis Ă jour!
Bon
const [count, setCount] = useState(0);
return <p>{count}</p>;
// Se met Ă jour!
Modifier ref.current ne déclenche PAS de re-render
C'est le but, pas un bug!
const count = useRef(0);
const increment = () => {
count.current += 1;
// L'UI ne se met PAS Ă jour
// C'est normal! Pas de re-render
};
Si tu veux un re-render, utilise useState
Au premier render, le DOM n'existe pas encore
Erreur
useEffect(() => {
inputRef.current.focus();
// TypeError si null!
}, []);
Sécurisé
useEffect(() => {
inputRef.current?.focus();
// Optional chaining!
}, []);
Utilisez ?. (optional chaining) pour sécuriser
Non-contrôlé = defaultValue, Contrôlé = value
Non-contrôlé
<input
ref={inputRef}
defaultValue="initial"
/>
Valeur initiale seulement
Contrôlé
<input
value={value}
onChange={handleChange}
/>
Valeur contrôlée par React
Mélanger les deux = comportement imprévisible
useRef = 2 usages
Accéder au DOM + valeurs mutables sans re-render
ref.current est MUTABLE
Le modifier ne déclenche PAS de re-render
Contrôlé vs Non-contrôlé
React possède la valeur vs DOM possède la valeur
forwardRef
Exposer une ref depuis un composant enfant
useRef = une "boîte" persistante entre les renders
DOM access = attacher ref à un élément JSX avec l'attribut ref
Valeur mutable = stocker des données "cachées" sans re-render
forwardRef = permettre au parent d'accéder au DOM d'un composant enfant
Règle d'or : Si la valeur doit être affichée dans le JSX, utilise useState!
1. Focus automatique
Créer un input qui reçoit le focus au montage
2. Compteur de clics caché
Compter les clics sans afficher le compte (console.log)
3. Input non-contrôlé
Créer un formulaire avec ref et defaultValue
4. Composant CustomInput avec forwardRef
Créer un input stylisé qui expose sa ref au parent
useRef : le hook pour le DOM et les valeurs "cachées"
Prochaine leçon : Hooks personnalisés