Retour • 15/10/2024
Tu n'as PAS besoin de useCallback en React
Écris par Melvyn Malherbe le 15/10/2024
Quand je rentre dans un codebase ou copie le code d'un projet Shadcn/UI je vois plein de useCallback
et après quelques minutes de réflexion, je me retrouve toujours à les supprimer.
Tu n'as généralement jamais besoin de useCallback
On va voir ensemble quand tu en as vraiment besoin et quand l'utiliser et plusieurs exemples de mauvaise utilisation de useCallback
.
C'est quoi useCallback
?
useCallback
est un hook React qui permet de stabiliser une fonction. Il faut comprendre que la référence d'une méthode change par défaut à chaque rendu de ton composant. Avec useCallback
, elle ne change QUE quand le tableau de dépendances change.
Quand utiliser useCallback
?
La majorité des cas, on utilise useCallback
pour stabiliser une fonction qui est utilisée dans 2 cas :
- Dans les dépendances d'un
useEffect
- Dans les dépendances d'un
memo()
Utilisation avec memo()
Le cas très courant est d'avoir un composant qui possède un state et un nombre composant, que j'ai appelé GiantCounter
qui est "long" et compliqué à charger pour React.
Dans le playground suivant :
- J'ai simulé un lag dans le composant
GiantCounter
avec unwhile
- J'ai mis
memo
autour de monGiantCounter
pour éviter qu'il render
memo
permet de limiter les render d'un
composant uniquement quand celui-ci a des props différentes.
Le problème que tu remarques c'est que mon composant render toujours, on peut le voir car l'application bug. C'est car la fonction onBigButtonClick
est redéfinie à chaque render.
C'est difficile à comprendre, car elle ne change pas en soi mais elle est bien redéfinie et remplacée par une fonction similaire.
Tu peux le comprendre ici :
On peut imaginer en quelque sorte que la méthode est constamment redéfinie. Ce problème peut-être résolu en utilisant useCallback
mais pas de cette manière :
const onBigButtonClick = useCallback(() => {
setCount(count + 1000);
}, [count]);
Ici le problème reste le même, car count
est constamment différent, il faut être encore plus malin et faire ceci :
const onBigButtonClick = useCallback(() => {
setCount((c) => c + 1000);
}, []);
Ici, notre méthode ne va jamais changer et "son id" restera stable. Tu peux voir que maintenant notre application ne bug plus :
Ce premier usage est de loin le plus utilisé de useCallback
. Il y a déjà une leçon qu'on a apprise :
Leçon 1 : Utiliser useCallback sans memo est inutile
Beaucoup de personnes utilisent des useCallback
alors même que le composant qui prend les paramètres n'est pas memo
. Il faut savoir que c'est totalement inutile. Car par défaut React suit la règle suivante :
- Quand un composant est rendu, tous ses enfants sont rendus aussi (sauf s'ils sont mémorisés)
useEvent
Mais j'ai une nouvelle encore pire... Même ici, l'utilisation de useCallback
est inutile.
Il y a une méthode magique que je dois te partager et ça s'appelle useEvent
, ici notre méthode onBigButtonClick
est un event, il vient être exécuté à un moment et c'est tout.
On peut donc wrapper ce callback dans useEvent
:
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useCallback, useEffect, useRef } from 'react';
type EventCallback<T extends any[]> = (...args: T) => void;
export const useEvent = <T extends any[]>(
callback: EventCallback<T>
): EventCallback<T> => {
const ref = useRef<EventCallback<T> | null>(null);
useEffect(() => {
ref.current = callback;
}, [callback]);
// Use useCallback to memoize the returned function
const triggerEvent = useCallback((...args: T) => {
ref.current?.(...args);
}, []);
return triggerEvent;
};
Cette méthode vient :
- Sauvegarder le callback dans un
ref
- Chaque fois que le callback change, on met à jour la
ref
(avec un useEffect) - On retourne une fonction qui va appeler le callback (qui utilise
useCallback
avec aucun paramètre)
Si tu ne comprends pas comment est utilisé useRef tu peux aller voir mon article.
Résultat ? On retourne une méthode 100% stable qui ne va jamais changer. Avec cette solution il est même possible d'utiliser count + 1000
dans le callback car celui-ci est une référence 100% stable.
Mais j'ai une nouvelle encore pire... généralement tu n'as même pas besoin useEvent
!
Pas de memo()
Ce qu'il faut challenger en premier, c'est l'utilisation abusive de memo()
. Un de mes élèves a utilisé memo()
pour ce composant :
const NumericInput = forwardRef<HTMLInputElement, NumericInputProps>(
({ min, max, label, suffix = '', onNavigate, onEnterPress }, ref) => {
const timeUnit = shortLabelToTimeUnit[label as ShortLabel];
const { value, handleKeyDown, handleChange } = useNumericInputLogic({
min,
max,
label: timeUnit,
onNavigate,
onEnterPress,
});
const { handleFocus, handleBlur, isFocused } = useFocusClass(
ref as React.RefObject<HTMLInputElement>,
'bg-[#894889]/70 text-white'
);
return (
<div className="flex flex-col items-center">
<label className="items-center mb-1 font-medium text-gray-200 text-sm">
{label}
</label>
<div className="flex items-center rounded-lg">
<input
// pleins de props
/>
<span className="text-4xl text-gray-100 cursor-default">{suffix}</span>
</div>
</div>
);
}
);
NumericInput.displayName = 'NumericInput';
export default React.memo(NumericInput);
memo
!Pour être honnête, il y en a très peu qui en ont besoin. C'est plus une anomalie qu'un pattern standard.
Les renders ne sont pas mauvais !
Il est normal que ton composant render souvent même s'il n'en a pas besoin, c'est la manière de React de faire en sorte que ton application soit toujours à jour.
Le problème c'est quand les render font laguer ton application ou sont trop à faire. Si ton render a un temps normal pour être effectué, tu n'as pas besoin de memo
et donc souvent, pas besoin de useCallback
.
Quand utiliser memo
?
- Quand un composant est lourd (souvent qu'il rend beaucoup de composants enfants)
- Quand un composant est render très souvent pour rien
Si les deux cas ne sont pas remplis : tu n'as pas besoin de memo
.
Conclusion
useCallback
est très souvent utilisé pour éviter que des composants mémorisés soient render... mais souvent il n'y a même pas besoin d'utiliser memo
.
Tous ces patterns c'est le genre de truc que je parle dans ma formation React que tu peux retrouver ici :
Le meilleur moyen d'apprendre React !
Rejoins par développeurs, cette formation reçoit une note de 4.7 / 5 🚀
Reçois 12 leçons premium pour maîtriser React et faire partie des meilleurs