useEffect

Les effets de bord en React

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

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

Plan du cours

1

Rappel semaine 3

Composants, state, listes - tout est synchrone pour l'instant

2

Le problème

Comment récupérer des données d'une API ? Envoyer des analytics ?

3

Introduction Ă  useEffect

Le hook pour les effets de bord (side effects)

4

Demo live

Fetch d'une API, puis timer avec cleanup

5

Erreurs classiques

Boucle infinie, cleanup manquant, valeurs stale

Rappel : Semaine 3

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 ?

Le problème

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

Qu'est-ce qu'un effet de bord ?

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

Exemples d'effets de bord

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

Le hook useEffect

Gérer les effets de bord dans les composants fonction

3 éléments à comprendre :

callback · dependencies · cleanup

Syntaxe de base

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

Anatomie complète de useEffect

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

Le tableau de dépendances

Contrôle QUAND l'effet s'exécute

L'effet ne s'exécute que si une dépendance change

3 cas du tableau de dépendances

useEffect(() => { ... });

Pas de tableau = Ă  CHAQUE render

useEffect(() => { ... }, []);

Tableau vide = une seule fois (mount)

useEffect(() => { ... }, [count]);

Avec dépendances = quand count change

Cas 1 : Pas de tableau

L'effet s'exécute après chaque render

useEffect(() => {

console.log('Render!');

});

Si le callback modifie le state = BOUCLE INFINIE

Cas 2 : Tableau vide []

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

Cas 3 : Avec dépendances

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

Cycle de vie visuel

Mount

Component mounted

-->

Update

Props/State changed

-->

Unmount

Component removed

useEffect(() => {}, []) -- Mount seulement
useEffect(() => {}, [dep]) -- Mount + Update
useEffect(() => { return cleanup }, []) -- Mount + Unmount cleanup

Le 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

Pourquoi le cleanup est OBLIGATOIRE

Sans cleanup = fuite mémoire

Timer

Le timer continue après unmount

Subscription

La connexion WebSocket reste ouverte

Event Listener

L'écouteur reste attaché au DOM

Le cleanup empêche ces problèmes!

Exemple : Timer avec cleanup

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é

Exemple : Event listener avec cleanup

useEffect(() => {

const handleResize = () => {

setWindowWidth(window.innerWidth);

};

window.addEventListener('resize', handleResize);

return () => {

window.removeEventListener('resize', handleResize);

};

}, []);

Pattern : addEventListener --> return --> removeEventListener

Fetch de données avec useEffect

Pattern classique pour les appels API

Étapes :

1. Loading state

2. Fetch data

3. Error handling

Pattern complet : Fetch avec loading

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

Bonus : AbortController pour annuler

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"

Quand NE PAS utiliser useEffect

Éviter les anti-patterns courants

Si tu peux calculer pendant le rendu...

Tu n'as PAS besoin de useEffect!

Anti-pattern #1 : Transformer des données

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

Anti-pattern #2 : Reset state sur prop change

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

Anti-pattern #3 : Logique dans useEffect au lieu d'event

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!

Résumé : Quand PAS de useEffect

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

Pièges courants

Les erreurs à éviter absolument

Boucle infinie

setState dans useEffect sans []

Valeur stale

Dépendance manquante

Piège #1 : La boucle infinie

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!

Piège #2 : Valeur stale (périmée)

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!

Piège #3 : Cleanup manquant

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

Points clés à retenir

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

Récapitulatif

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

Ă€ retenir!

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!

Exercices pratiques

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)

Questions?

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