useRef & forwardRef

DOM, valeurs mutables et composants réutilisables

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

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

Plan du cours

1

Rappel : useState déclenche un re-render

Mais parfois on veut stocker une valeur SANS re-render

2

Introduction Ă  useRef

Une "boîte" qui persiste entre les renders

3

Demo live : DOM access

Focus un input, scroller, mesurer un élément

4

Contrôlé vs Non-contrôlé

React possède la valeur vs DOM possède la valeur

5

forwardRef

Exposer une ref depuis un composant enfant

Rappel : useState

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...

Le problème

Comment accéder à un élément DOM ?

1

Focus automatiquement un input

2

Scroller vers un élément

3

Mesurer la taille d'un élément

4

Intégrer une librairie tierce (D3, animation...)

En vanilla JS : document.getElementById()

Mais en React ?

Le hook useRef

Une "boîte" qui persiste entre les renders

2 usages principaux :

Accéder au DOM · Stocker une valeur mutable

Syntaxe de base

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

useRef vs useState

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);

Usage 1 : Accéder au DOM

Connecter une ref à un élément JSX

Utiliser l'attribut ref

Exemple : Focus automatique

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

Comment ça marche ?

1

Créer la ref

const inputRef = useRef(null);

2

L'attacher à l'élément

<input ref={inputRef} />

3

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)

Exemple : Scroller vers un élément

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

Exemple : Mesurer un élément

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

Usage 2 : Valeurs mutables

Stocker une valeur sans re-render

Comme une variable "cachée" qui persiste

Exemple : Compteur de clics (sans affichage)

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!

Exemple : Stocker un timer ID

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

Quand utiliser useRef pour une valeur ?

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!

Contrôlé vs Non-contrôlé

Qui possède la valeur de l'input ?

Contrôlé

React possède la valeur

Non-contrôlé

Le DOM possède la valeur

Input contrôlé

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

Input non-contrôlé

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é vs Non-contrôlé : Quand utiliser ?

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="..." />

Le problème : forwardRef

Comment passer une ref Ă  un composant enfant ?

Un composant personnalisé ne peut pas recevoir de ref par défaut

Le problème

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

Solution : forwardRef

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()

Anatomie de forwardRef

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

Exemple complet : Input avec label

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

Pièges courants

Erreurs à éviter

4 erreurs fréquentes

Piège 1 : Utiliser useRef pour tout

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!

Piège 2 : Modifier ref.current et attendre un re-render

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

Piège 3 : ref.current peut être null

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

Piège 4 : Confondre defaultValue et value

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

Points clés à retenir

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

Ă€ retenir !

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!

Exercices pratiques

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

Questions?

useRef : le hook pour le DOM et les valeurs "cachées"

Prochaine leçon : Hooks personnalisés