🔄 Pourquoi useEffect ?
useEffect sert à synchroniser un composant avec des effets de bord — des opérations qui ne font pas partie du rendu JSX pur : fetch de données, timers, abonnements, manipulation du DOM...
- Appels API et fetch de données
- Abonnements (WebSocket, EventSource)
- Timers (
setTimeout,setInterval) - Modification du
document.title - Écouteurs d'événements sur
window
⚠️ Ne jamais effectuer d'effets de bord directement dans le corps du composant — ils seraient exécutés à chaque rendu et pourraient créer des boucles infinies.
📝 Syntaxe de base
import { useState, useEffect } from 'react';
function Exemple() {
const [count, setCount] = useState(0);
// Effet sans dépendances — s'exécute à chaque rendu
useEffect(() => {
document.title = 'Compteur : ' + count;
});
// Effet avec [] — s'exécute une seule fois (montage)
useEffect(() => {
console.log('Composant monté');
}, []);
// Effet avec dépendances — s'exécute quand count change
useEffect(() => {
console.log('count a changé :', count);
}, [count]);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
📌 Tableau de dépendances
Le deuxième argument de useEffect contrôle quand l'effet se ré-exécute.
// 1. Pas de tableau — s'exécute à CHAQUE rendu
useEffect(() => { /* ... */ });
// 2. Tableau vide [] — s'exécute UNE FOIS au montage
useEffect(() => {
// Idéal pour : fetch initial, abonnements one-shot
}, []);
// 3. Avec dépendances — s'exécute quand une dep change
useEffect(() => {
fetchUser(userId);
}, [userId]); // Re-exécute si userId change
// ⚠️ ESLint eslint-plugin-react-hooks vérifie que
// toutes les valeurs utilisées dans l'effet sont dans le tableau
✅ Règle : inclure dans le tableau toutes les valeurs du composant utilisées dans l'effet (state, props, fonctions).
🧹 Nettoyage (cleanup)
L'effet peut retourner une fonction de nettoyage qui s'exécute avant le prochain effet ou au démontage du composant.
// Timer — nettoyage pour éviter les fuites mémoire
useEffect(() => {
const intervalId = setInterval(() => {
setTemps(t => t + 1);
}, 1000);
return () => clearInterval(intervalId); // cleanup
}, []);
// EventListener sur window
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// AbortController pour annuler un fetch
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(data => setData(data))
.catch(err => { if (err.name !== 'AbortError') setError(err); });
return () => controller.abort(); // annule le fetch si le composant démonte
}, []);
🌐 Fetch de données avec useEffect
function ListeUtilisateurs() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchUsers() {
try {
setLoading(true);
const res = await fetch('https://jsonplaceholder.typicode.com/users', {
signal: controller.signal
});
if (!res.ok) throw new Error('Erreur réseau ' + res.status);
const data = await res.json();
setUsers(data);
} catch (err) {
if (err.name !== 'AbortError') setError(err.message);
} finally {
setLoading(false);
}
}
fetchUsers();
return () => controller.abort();
}, []); // [] = une seule fois au montage
if (loading) return <p>Chargement...</p>;
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}