useEffect & Cycle de Vie
Gère les effets de bord : appels API, timers, abonnements...
5.1Qu'est-ce qu'un effet de bord ?
Tout ce qui n'est pas "calculer du JSX" est un effet de bord :
- Appeler une API (fetch)
- Manipuler le DOM (document.title)
- Lancer un timer (setInterval, setTimeout)
- S'abonner à un WebSocket ou un événement
- Lire le localStorage
Ces opérations ne peuvent pas se faire pendant le rendu. Elles doivent se faire après que React a affiché le composant. C'est le rôle de useEffect.
5.2Syntaxe de useEffect
import { useEffect } from 'react'
useEffect(() => {
// Code exécuté APRÈS le rendu
// (l'effet)
return () => {
// Code exécuté au DÉMONTAGE
// (le cleanup, optionnel)
}
}, [/* dépendances */])Le tableau de dépendances contrôle quand l'effet se relance :
| Dépendances | Quand l'effet relance | Cas d'usage |
|---|---|---|
[] (vide) | 1 seule fois (au montage) | Fetch initial, abonnement |
[count] | Quand count change | Réaction à un state |
| Pas de tableau | À chaque rendu | Rarement voulu |
5.3Exemple : appel API
Le cas d'usage le plus courant : charger des données depuis une API quand le composant apparaît :
import { useState, useEffect } from 'react'
function ListeUtilisateurs() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function fetchUsers() {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error('Erreur réseau')
const data = await res.json()
setUsers(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur inconnue')
} finally {
setLoading(false)
}
}
fetchUsers()
}, []) // [] = s'exécute UNE seule fois au montage
if (loading) return <p>Chargement...</p>
if (error) return <p>Erreur : {error}</p>
return (
<ul>
{users.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}5.4Effet avec dépendances
L'effet se relance quand une de ses dépendances change :
function RechercheProduits() {
const [query, setQuery] = useState("")
const [resultats, setResultats] = useState([])
useEffect(() => {
if (query.length < 2) {
setResultats([])
return
}
// Recherche quand la query change (avec debounce natif)
const timer = setTimeout(async () => {
const res = await fetch(`/api/search?q=${query}`)
const data = await res.json()
setResultats(data)
}, 300)
// Cleanup : annule le timer si query re-change vite
return () => clearTimeout(timer)
}, [query]) // ← Se relance quand 'query' change
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Rechercher..."
/>
<ul>
{resultats.map((r: any) => (
<li key={r.id}>{r.name}</li>
))}
</ul>
</div>
)
}5.5Le cleanup : nettoyer après soi
Si ton effet crée quelque chose (timer, abonnement, event listener), tu dois le nettoyer quand le composant disparaît :
function Horloge() {
const [heure, setHeure] = useState(new Date())
useEffect(() => {
const interval = setInterval(() => {
setHeure(new Date())
}, 1000)
// Cleanup : arrête le timer quand le composant disparaît
return () => clearInterval(interval)
}, [])
return <p>Il est {heure.toLocaleTimeString()}</p>
}
// Avec un event listener
function TailleEcran() {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return <p>Largeur : {width}px</p>
}