---
title: "Chapitre 2 : useEffect"
weight: 410
---

# 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 <div>Mon composant</div>;
}
```

## 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 <p>Chargement...</p>;
    if (error) return <p>Erreur : {error}</p>;
    if (!user) return null;

    return <div>{user.name}</div>;
}
```

## 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 <p>{size.width} x {size.height}</p>;
}
```

### Exemple : timer

```javascript
function Timer() {
    const [seconds, setSeconds] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(s => s + 1);
        }, 1000);

        // Cleanup : arrêter le timer
        return () => {
            clearInterval(interval);
        };
    }, []);

    return <p>{seconds} secondes</p>;
}
```

## 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 <div>Test</div>;
}

// 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 (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(c => c + 1)}>+1</button>
        </div>
    );
}
```

### 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 <p>Chargement...</p>;
    if (error) return <p>Erreur : {error}</p>;

    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    );
}
```

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

[← Chapitre précédent](/training/module3-hooks-fondamentaux/usestate) | [Retour au module](/training/module3-hooks-fondamentaux) | [Chapitre suivant : useRef ->](/training/module3-hooks-fondamentaux/useref)
