← Retour aux articles

useCallback et useMemo : quand les utiliser vraiment ?

· 4 min de lecture

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é.

parent.tsx
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.

use-callback-example.ts
const handleClick = useCallback(() => {
  console.log('clicked', id);
}, [id]); // Recrée seulement si `id` change

Quand useCallback est utile

Cas 1 : Props d'un composant mémorisé avec React.memo

memoized-parent.tsx
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

use-data-fetcher.ts
// 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 pas

Quand useCallback ne sert à rien

simple-component.tsx
// ❌ 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.

use-memo-example.ts
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

product-list.tsx
// ✅ 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é

dashboard.tsx
// ✅ 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

counter.tsx
// ❌ 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 :

  1. D'abord, profilez votre application avec React DevTools. Ne pas optimiser à l'aveugle.

  2. useCallback : uniquement si la fonction est passée en prop à un composant mémorisé avec React.memo, ou si elle est dans les dépendances d'un useEffect.

  3. 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.