useCallback est un hook React qui retourne une version mémoïsée d'une fonction qui ne change pas entre les rendus jusqu'à ce que ses dépendances changent. Contrairement à une déclaration de fonction normale dans un composant (qui crée une nouvelle fonction à chaque rendu), useCallback stabilise la référence de la fonction, évitant ainsi les re-rendus inutiles des composants enfants optimisés avec React.memo. Selon React Documentation (2025), useCallback n'est utile qu'en combinaison avec React.memo ou des hooks qui dépendent d'une référence stable.
Points clés
useCallback est un hook ajouté dans React 16.8 qui mémoïse une fonction : il retourne la même référence jusqu'à ce que les dépendances changent. Sans useCallback, chaque déclaration de fonction dans un composant crée un nouvel objet fonction à chaque rendu. Pour les primitifs, cela est imperceptible, mais en passant ces callbacks à des composants enfants optimisés avec React.memo, chaque nouvelle référence provoque un re-rendu du composant enfant.
Syntaxiquement, useCallback est équivalent à useMemo pour une fonction : useCallback(fn, deps) est un raccourci pour useMemo(() => fn, deps). React stocke la fonction mémoïsée dans le stockage interne du nœud fiber et compare les dépendances à chaque rendu. Si les dépendances n'ont pas changé (Object.is pour chaque élément), la fonction précédente est retournée.
Selon React Documentation — useCallback (2025), il ne faut pas envelopper chaque fonction dans useCallback. Le hook a son coût : appeler le hook, comparer les dépendances et allouer de la mémoire pour le tableau de dépendances. Si un composant est simple et n'a pas d'arbres profonds avec React.memo, useCallback ne fera que ralentir l'application. L'optimisation doit être mesurable, pas intuitive.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Référence stable — même fonction jusqu'à ce que les deps changent
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
La mémoïsation dans useCallback est basée sur la mise en cache du résultat de l'appel de fonction. React préserve la fermeture (closure) créée lors du premier rendu et la retourne lors des rendus suivants tant que les dépendances restent inchangées. À l'intérieur du nœud fiber, chaque appel useCallback crée un nœud dans la liste chaînée des hooks, où les dépendances précédentes et la valeur mémoïsée sont stockées.
La comparaison des dépendances est effectuée strictement via Object.is — une comparaison superficielle sans vérification profonde des objets ou tableaux. Si une dépendance est un objet ou un tableau, une nouvelle référence à chaque rendu sera considérée comme un changement. Par conséquent, le tableau de dépendances doit contenir des valeurs primitives ou des références stables (par exemple, de useRef ou useMemo).
Selon React Core Team — Optimization Guide (2024), le coût de la mémoïsation comprend trois composants : allouer le tableau de dépendances à chaque rendu, parcourir et comparer les éléments via Object.is, et la surcharge potentielle du ramasse-miettes lors de la recréation. Pour un composant avec des centaines de wrappers useCallback, cela peut devenir perceptible — donc la sélectivité dans l'utilisation du hook est critique.
| Scénario | Sans useCallback | Avec useCallback |
|---|---|---|
| Création de fonction | Nouvelle à chaque rendu | Identique avec deps stables |
| Passé à React.memo | L'enfant se re-rend | L'enfant ne se re-rend pas |
| Dans le tableau de useEffect | L'effet redémarre | L'effet est stable |
| Surcharge | Minimale | Comparaison des dépendances + mémoire |
Il existe un mythe très répandu selon lequel useCallback améliore automatiquement les performances. En réalité, isolément (sans React.memo), useCallback ralentit même légèrement l'application en raison du coût de la comparaison des dépendances. Le hook n'apporte un réel bénéfice que dans trois scénarios : empêcher les re-rendus des composants React.memo, stabiliser les callbacks dans useEffect et passer des callbacks à des hooks personnalisés qui dépendent de l'égalité de référence.
La règle est simple : tant que vous n'avez pas détecté un problème de performance avec React DevTools Profiler — n'utilisez pas useCallback. L'équipe React a souligné à plusieurs reprises que l'optimisation prématurée est la racine de tous les maux. D'abord, écrivez du code propre sans mémoïsation, mesurez, trouvez le goulot d'étranglement dans le profileur, et alors seulement ajoutez useCallback là où c'est vraiment nécessaire.
// Optimisation mesurable : Child est enveloppé dans React.memo
const Child = React.memo(({ onClick }) => {
console.log('Child re-rendu');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('cliqué');
}, []);
return <Child onClick={handleClick} />;
}
Selon Dan Abramov — Before You memo() (2024), plus de 90% des cas d'utilisation de useCallback dans les projets open-source sont redondants. Les développeurs enveloppent chaque fonction « au cas où » sans mesurer l'effet. L'alternative : si le composant enfant est lourd et son re-rendu coûteux — React.memo + useCallback est justifié. Si le composant enfant est léger — le re-rendu est moins coûteux que la comparaison des dépendances.
Le premier scénario est React.memo. Si un composant enfant est enveloppé dans React.memo et reçoit une fonction de callback comme prop, sans useCallback, le composant enfant se re-rend à chaque rendu du parent, même si ses propres données n'ont pas changé. useCallback stabilise la référence, permettant à React.memo d'ignorer correctement le re-rendu.
Le deuxième scénario est useEffect avec un callback dans les dépendances. Si une fonction est passée dans le tableau de dépendances de useEffect, chaque nouvelle référence redémarre l'effet. useCallback garantit que la référence est stable et que l'effet ne s'exécute que lorsque les données réelles changent, pas à chaque rendu. Ceci est particulièrement important pour les abonnements et les requêtes.
// useCallback pour dépendance stable de useEffect
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // référence stable, jamais recréée
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // l'effet s'exécute seulement quand props.id change
La principale différence entre useCallback et useMemo est ce que chacun mémoïse. useCallback mémoïse une fonction : useCallback(fn, deps) retourne fn (la même ou la version précédente). useMemo mémoïse le résultat d'un appel de fonction : useMemo(() => computeExpensive(a, b), [a, b]) retourne la valeur calculée, pas une fonction.
Techniquement, useCallback est du sucre syntaxique sur useMemo : useCallback(fn, deps) est équivalent à useMemo(() => fn, deps). Cette syntaxe n'existe que pour la lisibilité — pour que le développeur voie clairement qu'une fonction est mémoïsée, pas une valeur. Il n'y a pas de différence de performance entre useCallback et useMemo avec une fonction — ils génèrent un code identique.
| Hook | Mémoïse | Syntaxe | Utilisation |
|---|---|---|---|
| useCallback | Fonction (référence) | useCallback(fn, deps) | Callbacks pour composants enfants |
| useMemo | Résultat de calcul | useMemo(() => value, deps) | Calculs coûteux, mémoïsation d'objets |
// Ceux-ci sont équivalents :
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
L'erreur la plus courante est d'envelopper inutilement toutes les fonctions dans useCallback sans React.memo sur les composants enfants. Si un composant enfant n'est pas enveloppé dans React.memo, il se re-rendra quand même à chaque rendu du parent, que la référence du callback change ou non. useCallback sans React.memo, c'est du coût sans bénéfice.
// ❌ Inutile : pas de React.memo sur l'enfant
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child se re-rend encore sans React.memo
// ❌ Stale closure : dépendance manquante
const handle = useCallback(() => {
console.log(count); // count est toujours 0 — stale closure !
}, []);
// ✅ Correct : inclure les dépendances
const handle = useCallback(() => {
console.log(count);
}, [count]);
Le problème de fermeture obsolète (stale closure) dans useCallback est résolu en incluant toutes les variables utilisées dans le tableau de dépendances. eslint-plugin-react-hooks avec exhaustive-deps vérifie automatiquement que toutes les variables du corps du callback sont présentes dans le tableau. Si le callback utilise setState, qui ne change pas entre les rendus, il peut être inclus en toute sécurité dans deps — React garantit la stabilité de setState.
Foire aux questions
Non. useCallback n'a de sens que dans trois cas : le composant enfant est enveloppé dans React.memo, la fonction est utilisée dans le tableau de dépendances de useEffect, ou la fonction est passée à un hook personnalisé qui dépend de l'égalité de référence. Dans les autres cas, useCallback ajoute une surcharge sans bénéfice. L'équipe React recommande d'écrire d'abord sans optimisations et de les ajouter en fonction des résultats du profilage.
Pour les composants simples — une nouvelle fonction à chaque rendu est légèrement plus rapide, car useCallback dépense des ressources pour comparer les dépendances et allouer le tableau. Pour les composants avec des arbres React.memo profonds, useCallback gagne en empêchant le re-rendu de milliers d'éléments enfants. Mesurez et comparez au lieu de deviner — utilisez React DevTools Profiler pour une évaluation objective.
Oui, useCallback fonctionne avec les fonctions async exactement de la même manière qu'avec les fonctions synchrones. Le hook mémoïse la fonction elle-même, et le résultat (une Promesse) est retourné à chaque appel. Une fonction async dans useCallback est un modèle courant pour les callbacks stables de chargement de données utilisés dans useEffect : const fetchData = useCallback(async (id) => {...}, []).
Utilisez React DevTools Profiler — il montre quels composants se re-rendent et pourquoi. Pour une vérification programmatique, ajoutez console.log ou utilisez useWhyDidYouUpdate — une bibliothèque qui consigne la raison du re-rendu. Les principales raisons : une prop a changé (y compris la référence du callback), l'état a changé ou le contexte a changé. Si useCallback n'aide pas — vérifiez que toutes les dépendances sont correctement spécifiées.
Même sans React.memo, useCallback peut être utile en combinaison avec useMemo pour les valeurs de contexte. Si vous passez un objet avec des fonctions à Context.Provider, enveloppez la création de l'objet dans useMemo et chaque fonction dans useCallback. Cela évite le re-rendu de tous les consommateurs du contexte lorsqu'une des fonctions change. Mais pour passer directement des callbacks dans des props sans React.memo, useCallback n'apporte aucun bénéfice.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi