useCallback et useMemo sont deux des hooks React les plus mal compris. Souvent utilisés par réflexe "pour optimiser", ils peuvent en réalité nuire aux performances s'ils sont utilisés partout sans discernement.
Dans cet article, je démonte les idées reçues et vous donne des règles claires pour savoir quand les utiliser.
Le problème de base : les re-renders en React
Avant tout, il faut comprendre comment React décide de re-rendre un composant. Par défaut, un composant se re-rend chaque fois que son composant parent se re-rend, indépendamment de si ses props ont changé.
function Parent() {
const [count, setCount] = useState(0);
// Cette fonction est recréée à chaque render
const handleClick = () => console.log('clicked');
return <Child onClick={handleClick} />;
}À chaque fois que count change, Parent se re-rend, handleClick est recrée, et Child reçoit une nouvelle référence de fonction → Child se re-rend aussi, même si rien n'a changé pour lui.
useCallback : mémoriser une fonction
useCallback retourne une version mémorisée d'une fonction. La fonction n'est recrée que si ses dépendances changent.
const handleClick = useCallback(() => {
console.log('clicked', id);
}, [id]); // Recrée seulement si `id` changeQuand useCallback est utile
Cas 1 : Props d'un composant mémorisé avec React.memo
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
return <button onClick={onClick}>Cliquer</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// Sans useCallback : Child re-rend à chaque fois (référence différente)
// Avec useCallback : Child ne re-rend pas si id n'a pas changé
const handleClick = useCallback(() => {
doSomething();
}, []); // Dépendances stables
return <Child onClick={handleClick} />;
}Cas 2 : Dépendance d'un useEffect
// Problème : fetchData est recrée à chaque render → useEffect boucle infiniment
useEffect(() => {
fetchData(id);
}, [fetchData]); // ← fetchData change à chaque render !
// Solution avec useCallback
const fetchData = useCallback(async (id: string) => {
const data = await api.get(id);
setData(data);
}, []); // fetchData stable → useEffect ne boucle pasQuand useCallback ne sert à rien
// ❌ Inutile : le composant se re-rend de toute façon
function SimpleComponent() {
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
// Si ce composant n'utilise pas React.memo et n'a pas de useEffect
// qui dépend de handleClick, useCallback ne sert à rien
return <button onClick={handleClick}>+</button>;
}useMemo : mémoriser une valeur calculée
useMemo mémorise le résultat d'un calcul. Il recalcule uniquement si les dépendances changent.
const expensiveResult = useMemo(() => {
return data.filter(item => item.active).sort((a, b) => a.score - b.score);
}, [data]);Quand useMemo est utile
Cas 1 : Calcul coûteux sur une grande liste
// ✅ Pertinent si `items` a des milliers d'éléments
const filteredItems = useMemo(() => {
return items
.filter(item => item.category === selectedCategory)
.sort((a, b) => b.date - a.date);
}, [items, selectedCategory]);Cas 2 : Objet passé en prop à un composant mémorisé
// ✅ Sans useMemo, config est un nouvel objet à chaque render
const config = useMemo(() => ({
theme: 'dark',
locale: locale,
}), [locale]);
return <MemoizedChart config={config} />;Quand useMemo est contre-productif
// ❌ Inutile : le calcul est trivial
const doubled = useMemo(() => count * 2, [count]);
// Écrivez simplement :
const doubled = count * 2;La mémorisation elle-même a un coût (stocker la valeur, comparer les dépendances). Pour des calculs simples, ce coût est supérieur au bénéfice.
La règle d'or
Voici comment je décide d'utiliser ces hooks :
-
D'abord, profilez votre application avec React DevTools. Ne pas optimiser à l'aveugle.
-
useCallback: uniquement si la fonction est passée en prop à un composant mémorisé avecReact.memo, ou si elle est dans les dépendances d'unuseEffect. -
useMemo: uniquement si le calcul est mesurablement lent, ou si la valeur est un objet/tableau passé en prop à un composant mémorisé.
Conclusion
La tentation d'envelopper toutes vos fonctions dans useCallback et tous vos calculs dans useMemo est forte, ça donne l'impression d'écrire du code "propre et optimisé". En réalité, c'est souvent l'inverse.
React est déjà très rapide. Faites confiance au moteur de rendu, profilez avant d'optimiser, et n'utilisez ces hooks que quand vous avez une raison mesurable de le faire.