
Ça fait des années que tu utilises useState, useEffect, et useContext. C'est devenu un réflexe. Tu les tapes sans réfléchir.
C'est exactement le problème.
Ces trois hooks sont responsables de 90% des bugs que j'ai debuggé en prod. Pas parce qu'ils sont cassés, mais parce qu'on les a tous mal appris dès le départ.
Voici ce qu'on ne t'a jamais dit quand tu as commencé.
useState : Le mensonge qu'on t'a raconté
Tous les tutos enseignent ça :
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
Simple. Propre. Cassé.
Clique assez vite sur ce bouton, et tu verras qu'il saute des chiffres. Pourquoi ? Parce que count est un snapshot, pas une valeur live. Quand React batch tes clics, ils lisent tous le même count périmé.
Le fix qu'on aurait dû t'apprendre dès le début
const [count, setCount] = useState(0);
function increment() {
setCount(prevCount => prevCount + 1);
}
Le pattern de mise à jour fonctionnelle n'est pas "avancé." C'est la façon par défaut dont tu devrais écrire tes mises à jour de state.
💡 Règle d'or : Si ton nouveau state dépend de l'ancien, utilise une fonction.
Le piège des objets
Celui-là attrape tout le monde :
const [user, setUser] = useState({ name: 'Samy', age: 25 });
// ❌ Ça ne fait rien du tout
function birthday() {
user.age += 1;
setUser(user);
}
React utilise Object.is() pour comparer les states. Même référence ? Pas de re-render. Tu as muté l'objet mais donné à React exactement la même adresse mémoire.
// ✅ Nouvel objet, nouvelle référence, React re-render
function birthday() {
setUser(prev => ({ ...prev, age: prev.age + 1 }));
}
Le modèle mental
Pense à useState comme une photo, pas un flux vidéo live.
À chaque render, tu regardes un moment figé dans le temps. La valeur ne changera pas en plein render, peu importe combien de setCount tu appelles.
useEffect : Le hook le plus mal compris
J'ai vu des devs seniors écrire ça :
useEffect(() => {
fetchUserData();
}, []);
"Le tableau de dépendances vide veut dire que ça run une seule fois au mount."
Ouais. Mais pourquoi ça run une seule fois ? Le tableau de dépendances fait quoi exactement ?
Ce n'est pas "quand exécuter"
Le tableau de dépendances est une liste des valeurs que ton effect lit depuis le scope du render.
function Profile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // L'effect lit userId, donc c'est une dépendance
}
Quand tu mens sur les dépendances, tu casses la synchronisation :
// ❌ Tu mens - cet effect lit userId mais ne le déclare pas
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // Bug: fetch toujours le premier userId, ignore les changements
Le cleanup que personne n'écrit
Voici une race condition qui se cache en plein jour :
useEffect(() => {
fetchUser(userId).then(data => {
setUser(data); // Et si userId a changé avant que ça resolve ?
});
}, [userId]);
Si l'utilisateur navigue assez vite, tu afficheras des données périmées. Le fix :
useEffect(() => {
let cancelled = false;
fetchUser(userId).then(data => {
if (!cancelled) {
setUser(data);
}
});
return () => {
cancelled = true; // Cleanup: ignore les réponses périmées
};
}, [userId]);
Cette fonction de cleanup n'est pas une décoration optionnelle. C'est du code porteur.
Le modèle mental
Pense à useEffect comme de la synchronisation, pas du lifecycle.
Ton effect est le pont entre le monde de React (state, props) et le monde extérieur (APIs, subscriptions, manipulation DOM). Le cleanup détruit le pont avant d'en construire un nouveau.
Quand NE PAS utiliser useEffect
C'est probablement la partie la plus importante. Tu n'as pas besoin de useEffect pour :
Transformer des données pour le render :
// ❌ Anti-pattern
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter(i => i.active));
}, [items]);
// ✅ Calcule-le directement
const filteredItems = items.filter(i => i.active);
Gérer des événements utilisateur :
// ❌ Surengineering
const [query, setQuery] = useState('');
useEffect(() => {
search(query);
}, [query]);
// ✅ Appelle-le simplement dans le handler
function handleSearch(e) {
const value = e.target.value;
setQuery(value);
search(value);
}
Réinitialiser le state quand les props changent :
// ❌ Effect pour reset le state
useEffect(() => {
setComment('');
}, [postId]);
// ✅ La prop key force une nouvelle instance
<CommentForm key={postId} />
useContext : Le tueur de prop drilling (qui peut tuer la performance)
Context semble magique. Passer des données à travers l'arbre de composants sans props ? J'achète.
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<DeepNestedComponent />
</ThemeContext.Provider>
);
}
function DeepNestedComponent() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>Hello</div>;
}
Magnifique. Jusqu'à ce que ton app rame.
La tempête de re-renders cachée
Chaque fois que la valeur du context change, tous les consumers re-render. Même s'ils n'utilisent qu'une partie de la valeur.
// ❌ Ça re-render TOUT au MOINDRE changement
<ThemeContext.Provider value={{ theme, user, settings, setTheme }}>
Le problème ? Chaque render crée une nouvelle référence d'objet. React voit un objet différent, déclenche des re-renders partout.
Le fix : stabilise tes valeurs
function App() {
const [theme, setTheme] = useState('light');
// Mémorise la valeur du context
const contextValue = useMemo(
() => ({ theme, setTheme }),
[theme] // Ne recrée que quand theme change
);
return (
<ThemeContext.Provider value={contextValue}>
<Children />
</ThemeContext.Provider>
);
}
Le modèle mental
Pense au context comme un système de broadcast :
Quand le DJ (Provider) change la musique (value), tout le monde dans le club (consumers) réagit. Qu'ils aient voulu entendre cette chanson ou non.
Quand séparer les contexts
Si différentes parties de ton app s'intéressent à des données différentes, sépare-les :
// Au lieu d'un mega-context
const AppContext = createContext({ user, theme, settings });
// Sépare par responsabilité
const UserContext = createContext(null);
const ThemeContext = createContext('light');
const SettingsContext = createContext({});
Maintenant les changements de thème ne re-render pas les composants qui ne s'intéressent qu'aux données user.
La cheat sheet
| Hook | Modèle Mental | Erreur Courante | Fix |
|---|---|---|---|
useState | Snapshot, pas valeur live | Muter les objets | Toujours créer de nouvelles références |
useEffect | Synchronisation, pas lifecycle | Mentir sur les dépendances | Liste tout ce que tu lis |
useContext | Broadcast à tous les consumers | Passer de nouveaux objets à chaque render | Mémorise les valeurs du context |
La suite ?
Ces trois hooks gèrent 80% de ton code React. Mais quand ils ne suffisent plus, quand la performance compte, quand tu as besoin d'accès au DOM, quand la logique de state devient complexe... c'est là que le tier suivant de hooks devient essentiel.
Dans le prochain article, on couvrira useRef, useMemo, et useCallback. Les hooks que tu devrais utiliser plus souvent.
Ce ne sont pas des "hooks d'optimisation." Ce sont des hooks de correction qui améliorent aussi la performance.
Continuer vers la partie 2 : Les hooks que tu devrais utiliser plus →
Des questions ? Trouvé un bug dans mon code ? Contacte-moi sur LinkedIn ou découvre plus sur mon blog.