# Chapitre 2 : useEffect
> Ce chapitre est **crucial**. useEffect est le hook le plus complexe à maîtriser, mais aussi le plus utilisé après useState.
## Qu'est-ce qu'un effet de bord ?
Un effet de bord (side effect) est toute opération qui affecte quelque chose en dehors du composant :
- Appeler une API
- Modifier le titre de la page
- S'abonner à un événement (WebSocket, resize, scroll)
- Manipuler le DOM directement
- Utiliser un timer (setTimeout, setInterval)
Ces opérations ne peuvent pas être faites directement dans le corps du composant, car celui-ci est exécuté à chaque rendu.
## Syntaxe de base
```javascript
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Code exécuté après le rendu
console.log('Composant rendu !');
});
return
Mon composant
;
}
```
## Le tableau de dépendances
Le deuxième argument de useEffect contrôle **quand** l'effet s'exécute :
### Sans tableau : à chaque rendu
```javascript
useEffect(() => {
console.log('Exécuté après CHAQUE rendu');
});
```
**Rarement utile** - peut causer des problèmes de performance.
### Tableau vide : une seule fois
```javascript
useEffect(() => {
console.log('Exécuté UNE SEULE FOIS après le premier rendu');
}, []);
```
**Équivalent à componentDidMount** en classes.
### Avec dépendances : quand elles changent
```javascript
useEffect(() => {
console.log('Exécuté quand userId change');
fetchUser(userId);
}, [userId]);
```
L'effet s'exécute :
1. Après le premier rendu
2. Après chaque rendu où `userId` a changé
## Exemple concret : appel API
```javascript
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Fonction async dans useEffect
const fetchUser = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // Re-fetch quand userId change
if (loading) return
Chargement...
;
if (error) return
Erreur : {error}
;
if (!user) return null;
return
{user.name}
;
}
```
## Fonction de nettoyage (cleanup)
useEffect peut retourner une fonction qui sera appelée :
- Avant la prochaine exécution de l'effet
- Quand le composant est démonté
```javascript
useEffect(() => {
// Setup
const subscription = api.subscribe(data => {
setData(data);
});
// Cleanup
return () => {
subscription.unsubscribe();
};
}, []);
```
### Exemple : événement de redimensionnement
```javascript
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
// Cleanup : retirer l'écouteur
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // [] car on veut s'abonner une seule fois
return
;
}
```
## Pièges courants
### 1. Boucle infinie
```javascript
// DANGER ! Boucle infinie
useEffect(() => {
setCount(count + 1); // Modifie l'état
}); // Pas de tableau = exécuté à chaque rendu
// Modifie l'état → re-rendu → useEffect → modifie l'état → ...
```
**Solution** : toujours mettre un tableau de dépendances.
### 2. Dépendances manquantes
```javascript
// PROBLÈME : userId n'est pas dans les dépendances
useEffect(() => {
fetchUser(userId);
}, []); // Fetch uniquement au montage, ignore les changements de userId
```
**Solution** : ajouter toutes les valeurs utilisées dans le tableau.
```javascript
useEffect(() => {
fetchUser(userId);
}, [userId]); // Correct
```
### 3. Objet ou fonction dans les dépendances
```javascript
// PROBLÈME : l'objet est recréé à chaque rendu
function MyComponent({ user }) {
const options = { limit: 10 }; // Nouvel objet à chaque rendu
useEffect(() => {
fetchData(options);
}, [options]); // options change à chaque rendu !
}
```
**Solution** : déplacer hors du composant ou utiliser useMemo (module suivant).
### 4. Async dans useEffect
```javascript
// INCORRECT - useEffect ne peut pas être async
useEffect(async () => { // Erreur !
const data = await fetchData();
}, []);
// CORRECT - définir une fonction async à l'intérieur
useEffect(() => {
const loadData = async () => {
const data = await fetchData();
setData(data);
};
loadData();
}, []);
```
## Ordre d'exécution
```javascript
function MyComponent() {
console.log('1. Rendu');
useEffect(() => {
console.log('3. Effet');
return () => console.log('2. Cleanup (si re-rendu)');
});
return
Test
;
}
// Premier rendu :
// 1. Rendu
// 3. Effet
// Re-rendu :
// 1. Rendu
// 2. Cleanup
// 3. Effet
```
## Comparaison avec le cycle de vie PHP
^ Événement ^ PHP ^ React useEffect ^
| Initialisation | constructeur | `useEffect(() => {}, [])` |
| À chaque requête | code du contrôleur | `useEffect(() => {})` |
| Nettoyage | destructeur | fonction retournée par useEffect |
## Quand utiliser useEffect ?
**OUI** :
- Appels API
- Abonnements (WebSocket, events)
- Timers
- Modifier le DOM directement
- Synchroniser avec un système externe
**NON** :
- Calculer une valeur dérivée de l'état → useMemo
- Transformer des données pour le rendu → faire dans le rendu
- Réagir à une action utilisateur → gestionnaire d'événement
## Exercices
### Exercice 1 : Titre de page dynamique
Créer un composant qui met à jour le titre de la page avec le compteur.
**Solution :**
```javascript
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Compteur : ${count}`;
}, [count]);
return (
{count}
);
}
```
### Exercice 2 : Fetch avec gestion d'erreur
Créer un composant qui charge une liste de posts depuis une API.
**Solution :**
```javascript
function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) throw new Error('Erreur réseau');
const data = await response.json();
setPosts(data.slice(0, 10));
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchPosts();
}, []);
if (loading) return
Chargement...
;
if (error) return
Erreur : {error}
;
return (
{posts.map(post => (
{post.title}
))}
);
}
```
## Points clés à retenir
1. **useEffect** exécute du code **après** le rendu
2. **Tableau vide `[]`** = une seule fois au montage
3. **Avec dépendances** = quand ces valeurs changent
4. **Fonction de retour** = nettoyage (cleanup)
5. **Toujours lister les dépendances** utilisées dans l'effet
6. **Async** : définir une fonction async à l'intérieur, pas sur useEffect
[[:15_training:module3-hooks-fondamentaux:usestate|← Chapitre précédent]] | [[:15_training:module3-hooks-fondamentaux:start|Retour au module]] | [[:15_training:module3-hooks-fondamentaux:useref|Chapitre suivant : useRef →]]