Les effets de bord en React
Utilisez les flèches, cliquez ou glissez pour naviguer
1. Comprendre les effets de bord
Side effects : tout ce qui n'est pas du rendu pur
2. Anatomie de useEffect
Callback, dependencies, cleanup
3. Le tableau de dépendances
Contrôler QUAND l'effet s'exécute
4. Le cleanup
Éviter les fuites mémoire
5. Fetch de données
Appels API avec useEffect
6. Quand NE PAS utiliser useEffect
Éviter les anti-patterns
Rappel semaine 3
Composants, state, listes - tout est synchrone pour l'instant
Le problème
Comment récupérer des données d'une API ? Envoyer des analytics ?
Introduction Ă useEffect
Le hook pour les effets de bord (side effects)
Demo live
Fetch d'une API, puis timer avec cleanup
Erreurs classiques
Boucle infinie, cleanup manquant, valeurs stale
Jusqu'ici, tout était synchrone
Component
Props, State
Listes
.map(), key
Événements
onClick, onChange
Mais comment faire pour...
Récupérer des données d'une API ?
Envoyer des analytics ?
Écouter le resize de la fenêtre ?
Lancer un timer ?
Les composants React sont des fonctions pures
Elles retournent du JSX basé sur les props et le state
function MonComposant({ name }) {
// Rendu pur : input props/state => output JSX
return <h1>Bonjour {name}</h1>;
}
Mais parfois on doit faire des choses en dehors du rendu...
Side Effect = tout ce qui n'est pas du rendu pur
Rendu pur
Calculer et afficher
Props + State = JSX
Effet de bord
Interagir avec le monde extérieur
API, DOM, timers...
Appels API / Fetch
Récupérer des données d'un serveur
Timers
setTimeout, setInterval
Event Listeners
resize, scroll, keydown
Subscriptions
WebSockets, observables
Manipulation du DOM
Modifier directement le DOM
Analytics / Logging
Tracker des événements
Tous ces cas nécessitent useEffect
Gérer les effets de bord dans les composants fonction
3 éléments à comprendre :
callback · dependencies · cleanup
import { useEffect } from 'react';
useEffect(() => {
// Code de l'effet (callback)
});
Le callback s'exécute après chaque render
Sans tableau de dépendances = exécution à CHAQUE render
useEffect(() => {
// 1. Callback : le code à exécuter
console.log('Effet exécuté!');
return () => {
// 2. Cleanup : nettoyer avant le prochain effet
console.log('Cleanup!');
};
}, [
// 3. Dependencies : quand exécuter
dependency1, dependency2
]);
Callback
Le code à exécuter
Cleanup
Nettoyer (optionnel)
Dependencies
ContrĂ´ler le QUAND
Contrôle QUAND l'effet s'exécute
L'effet ne s'exécute que si une dépendance change
useEffect(() => { ... });
Pas de tableau = Ă CHAQUE render
useEffect(() => { ... }, []);
Tableau vide = une seule fois (mount)
useEffect(() => { ... }, [count]);
Avec dépendances = quand count change
L'effet s'exécute après chaque render
useEffect(() => {
console.log('Render!');
});
Si le callback modifie le state = BOUCLE INFINIE
L'effet s'exécute une seule fois au mount
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
Idéal pour : fetch initial, setup unique, analytics au chargement
L'effet s'exécute quand userId change
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
L'effet se déclenche : mount + chaque fois que userId change
Mount
Component mounted
Update
Props/State changed
Unmount
Component removed
useEffect(() => {}, []) -- Mount seulement
useEffect(() => {}, [dep]) -- Mount + Update
useEffect(() => { return cleanup }, []) -- Mount + Unmount cleanup
Nettoyer avant que ce ne soit trop tard
Le cleanup s'exécute avant :
1. Le prochain effet (si dépendances changent)
2. Le unmount du composant
Sans cleanup = fuite mémoire
Le timer continue après unmount
La connexion WebSocket reste ouverte
L'écouteur reste attaché au DOM
Le cleanup empêche ces problèmes!
useEffect(() => {
// Setup : démarrer le timer
const timer = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup : arrĂŞter le timer
return () => {
clearInterval(timer);
};
}, []);
Le timer est proprement arrêté si le composant est démonté
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
Pattern : addEventListener --> return --> removeEventListener
Pattern classique pour les appels API
Étapes :
1. Loading state
2. Fetch data
3. Error handling
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => {
if (!res.ok) throw new Error('Erreur!');
return res.json();
})
.then(data => setUsers(data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
Les 3 états : loading, data, error
Annuler le fetch si le composant est démonté
useEffect(() => {
const controller = new AbortController();
fetch('/api/users', {
signal: controller.signal
})
.then(...)
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});
return () => controller.abort();
}, []);
Évite les warnings "Can't perform state update on unmounted component"
Éviter les anti-patterns courants
Si tu peux calculer pendant le rendu...
Tu n'as PAS besoin de useEffect!
Avec useEffect
const [items, setItems] = useState([]);
const [sorted, setSorted] = useState([]);
useEffect(() => {
setSorted(items.toSorted());
}, [items]);
Inutile et cause un re-render
Pendant le rendu
const [items, setItems] = useState([]);
const sorted = items.toSorted();
// Calculé pendant le render
Simple et efficace!
Les transformations synchrones se font pendant le rendu, pas dans useEffect
Avec useEffect
useEffect(() => {
setDraft(userId);
}, [userId]);
Synchronisation manuelle
Avec key
<Profile key={userId} />
React remonte le composant automatiquement!
Quand la prop change, changer la key force un nouveau mount
Mauvais : useEffect
useEffect(() => {
if (submitted) {
postToAPI(data);
setSubmitted(false);
}
}, [submitted]);
Bon : Event handler
const handleSubmit = () => {
postToAPI(data);
};
<button onClick={handleSubmit}>
Les événements utilisateur = handlers, pas effets!
Transformer des données
Fais-le pendant le rendu : const sorted = items.toSorted()
Reset state sur prop change
Utilise key={prop} pour forcer un nouveau mount
Logique déclenchée par un événement
Utilise un event handler direct : onClick={handleClick}
useEffect = side effects uniquement!
API, timers, subscriptions, event listeners DOM
Les erreurs à éviter absolument
Boucle infinie
setState dans useEffect sans []
Valeur stale
Dépendance manquante
useEffect sans [] + setState = BOUCLE INFINIE
Boucle infinie
useEffect(() => {
setCount(count + 1);
}); // Pas de []!
render --> effect --> setState --> render --> ...
Corrigé
useEffect(() => {
setCount(c => c + 1);
}, []); // Une seule fois
Règle : Toujours réfléchir aux dépendances!
Accès à une prop/state qui a changé, mais l'effet ne se met pas à jour
Stale value
useEffect(() => {
console.log(userId);
}, []); // userId pas listé!
userId reste Ă sa valeur initiale
Corrigé
useEffect(() => {
console.log(userId);
}, [userId]); // Dépendance!
ESLint exhaustive-deps aide à détecter ces problèmes!
Timer qui continue après unmount = fuite mémoire
Sans cleanup
useEffect(() => {
setInterval(() => {
setTime(Date.now());
}, 1000);
}); // Pas de return!
Le timer tourne indéfiniment
Avec cleanup
useEffect(() => {
const timer = setInterval(...);
return () => clearInterval(timer);
}, []);
Timer arrêté au unmount
useEffect = side effects
Tout ce qui n'est pas du rendu pur : API, timers, subscriptions, DOM
Le tableau de dépendances contrôle QUAND
[] = une fois | [dep] = quand dep change | pas de tableau = chaque render
Cleanup OBLIGATOIRE pour timers, subscriptions, event listeners
return () => { /* cleanup */ }
Si tu peux calculer pendant le render, PAS de useEffect
Transformations de données, reset sur prop change = pas d'effet
useEffect
Gérer les side effects
API, timers, subscriptions
Dépendances
Contrôler l'exécution
[] | [dep] | rien
Cleanup
Éviter les fuites
return () => {}
Anti-patterns
Quand PAS utiliser
Calculs, events
useEffect = ton ami pour les side effects!
Mais ne l'utilise pas pour tout
Bon usage
Fetch API
setTimeout / setInterval
addEventListener
Subscriptions WebSocket
Analytics / Logging
Mauvais usage
Transformer des données
Reset state sur prop change
Logique d'événement
Calculs synchrones
Si c'est synchrone et calculable --> pas de useEffect!
1. Fetch simple
Afficher une liste d'utilisateurs depuis jsonplaceholder avec loading state
2. Timer avec cleanup
Créer un compteur qui s'incrémente chaque seconde, avec bouton start/stop
3. Window resize
Afficher la largeur de la fenêtre en temps réel avec cleanup
4. Recherche avec debounce
Fetcher des résultats quand l'utilisateur tape (avec délai de 500ms)
useEffect est ton ami pour les side effects
Mais n'oublie pas : pas de cleanup = fuite mémoire!
Et si tu peux calculer pendant le render, pas besoin d'effet