Comprends les effets secondaires dans React, maîtrise le hook useEffect avec son tableau de dépendances, et apprends les patterns courants pour récupérer des données, gérer des timers et nettoyer les ressources.
Les composants React sont principalement responsables de calculer et retourner l'interface utilisateur basée sur leurs props et leur state. C'est la logique de rendu du composant — elle devrait être pure, ce qui signifie qu'elle produit le même résultat pour les mêmes entrées sans modifier quoi que ce soit en dehors d'elle-même.
Cependant, les applications réelles doivent faire plus que simplement afficher l'interface. Elles doivent :
Ces opérations sont appelées effets secondaires car elles affectent des choses en dehors du résultat de rendu du composant. Elles interagissent avec le "monde extérieur" — le navigateur, le réseau ou d'autres systèmes.
Tu ne devrais pas exécuter d'effets secondaires directement dans la phase de rendu d'un composant. Cela peut causer des bugs, des boucles infinies et un comportement imprévisible. À la place, React fournit le hook useEffect pour gérer les effets secondaires en toute sécurité.
// Mauvais : effet secondaire pendant le rendu
function BadComponent() {
document.title = 'Hello'; // S'exécute à chaque rendu — pas idéal
return <p>Hello</p>;
}
// Correct : effet secondaire dans useEffect
function GoodComponent() {
React.useEffect(() => {
document.title = 'Hello';
}, []);
return <p>Hello</p>;
}Le hook useEffect prend deux arguments :
React.useEffect(() => {
// Code d'effet secondaire ici
}, [dependencies]);Le tableau de dépendances détermine quand l'effet se ré-exécute :
1. Tableau vide [] — s'exécute une fois au montage :
React.useEffect(() => {
console.log('Composant monté !');
// S'exécute une fois, après le premier rendu
}, []);C'est l'équivalent de componentDidMount dans les composants de classe. Utilise ceci pour une configuration unique comme récupérer des données initiales ou établir un abonnement.
2. Tableau avec dépendances [a, b] — s'exécute quand les dépendances changent :
React.useEffect(() => {
console.log('Count a changé à :', count);
document.title = `Count: ${count}`;
}, [count]);L'effet s'exécute après le premier rendu et chaque fois que count change. React compare la valeur actuelle de chaque dépendance avec sa valeur précédente en utilisant Object.is(). Si une dépendance a changé, l'effet s'exécute à nouveau.
3. Pas de tableau de dépendances — s'exécute après chaque rendu :
React.useEffect(() => {
console.log('Composant rendu');
// S'exécute après CHAQUE rendu — utilise avec parcimonie !
});C'est rarement ce que tu veux. Sans tableau de dépendances, l'effet s'exécute après chaque rendu, ce qui peut causer des problèmes de performance ou des boucles infinies si l'effet met à jour le state.
Règles pour les dépendances :
count, inclus count dans le tableau.react-hooks/exhaustive-deps) peut t'aider à détecter les dépendances manquantes.Certains effets secondaires doivent être nettoyés quand le composant est démonté ou avant que l'effet ne se ré-exécute. Par exemple, si tu définis un timer, tu dois le supprimer. Si tu t'abonnes à un événement, tu dois te désabonner.
La fonction de configuration peut retourner une fonction de nettoyage. React appellera cette fonction de nettoyage :
React.useEffect(() => {
// Configuration : démarrer un timer
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
// Nettoyage : arrêter le timer
return () => {
clearInterval(timer);
};
}, []); // Deps vide : configure une fois, nettoie au démontageÉcouteurs d'événements :
React.useEffect(() => {
function handleResize() {
console.log('Taille de la fenêtre :', window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);Pourquoi le nettoyage est important : Sans nettoyage, tu obtiens des fuites mémoire. Si un composant se monte et se démonte plusieurs fois (par exemple, en naviguant entre les pages), chaque montage créerait un nouveau timer ou écouteur sans supprimer les anciens. Avec le temps, cela dégrade les performances et cause des bugs.
Nettoyage avec dépendances :
Quand un effet a des dépendances et se ré-exécute, React appelle le nettoyage de l'effet précédent avant d'exécuter le nouveau :
React.useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => {
connection.disconnect(); // Déconnexion de l'ancienne salle
};
}, [roomId]); // Se ré-exécute quand roomId changeQuand roomId change : déconnexion de l'ancienne salle (nettoyage), puis connexion à la nouvelle salle (configuration).
Voici les patterns les plus courants pour useEffect :
1. Mettre à jour le titre du document :
function PageTitle({ title }) {
React.useEffect(() => {
document.title = title;
}, [title]);
return <h1>{title}</h1>;
}2. Récupérer des données au montage :
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
let cancelled = false;
async function fetchUser() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (!cancelled) {
setUser(data);
}
}
fetchUser();
return () => {
cancelled = true; // Empêche la mise à jour du state si le composant est démonté
};
}, [userId]);
if (!user) return <p>Chargement...</p>;
return <p>{user.name}</p>;
}Le flag cancelled empêche de définir le state après que le composant a été démonté, ce qui causerait un avertissement React.
3. Définir un timer :
function Clock() {
const [time, setTime] = React.useState(new Date());
React.useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
return <p>{time.toLocaleTimeString()}</p>;
}4. Écouter des événements du navigateur :
function ScrollTracker() {
const [scrollY, setScrollY] = React.useState(0);
React.useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <p>Défilé : {scrollY}px</p>;
}5. Synchroniser avec localStorage :
function usePersistentState(key, initial) {
const [value, setValue] = React.useState(() => {
const saved = localStorage.getItem(key);
return saved !== null ? JSON.parse(saved) : initial;
});
React.useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React;
function App() {
const [count, setCount] = useState(0);
const [mounted, setMounted] = useState(false);
// Effect that runs once on mount
useEffect(() => {
setMounted(true);
document.title = 'useEffect Demo';
}, []);
// Effect that runs when count changes
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<h2>useEffect Demo</h2>
<p>Component mounted: {mounted ? 'Yes' : 'No'}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p><em>Check the browser tab title — it updates with the count!</em></p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Que signifie un tableau de dépendances vide [] dans useEffect ?