Un hook personnalisé est une fonction dont le nom commence par use et qui appelle d'autres hooks. Vous en écrivez un quand deux composants ont besoin de la même logique avec état : déplacez les appels à useState et useEffect dans la fonction, renvoyez ce dont le composant a besoin, et appelez-la depuis chaque composant.
Les deux spoilers appellent useToggle, et en ouvrir un laisse l'autre fermé. Chaque appel exécute son propre useState, donc chaque composant obtient sa propre valeur. Un hook personnalisé partage la logique, pas l'état.
Les règles pour en écrire un
- Le nom commence par
usesuivi d'une majuscule :useToggle,useOnlineStatus. La règle de lint des hooks se sert du préfixe pour trouver les appels de hooks et les vérifier. - Il appelle au moins un hook. Un utilitaire qui n'appelle aucun hook est une fonction ordinaire ; nommez-le
formatDate, pasuseFormatDate, pour pouvoir l'appeler n'importe où, y compris dans des conditions. - Il suit les règles des hooks : appeler les hooks à son niveau supérieur, et ne l'appeler que depuis des composants ou d'autres hooks personnalisés.
- Il renvoie ce qui est utile. Un tableau (
[on, toggle]) est pratique quand les appelants renomment les valeurs ; un objet ({ data, loading, error }) se lit mieux à partir de trois valeurs.
useLocalStorage : un état qui survit au rechargement
useState oublie tout au rechargement. Ce hook lit la valeur de départ dans localStorage et y réécrit chaque changement.
L'aperçu s'exécute dans un cadre isolé où le navigateur bloque localStorage : vous verrez donc ici l'avertissement dans la console, et le hook se rabat sur un état simple. Dans une page normale, le nom survit au rechargement. Le try/catch ne sert pas qu'à cet aperçu : la navigation privée, les cookies bloqués et un quota plein font aussi lever une erreur au stockage dans les vraies applications. Passer une fonction à useState signifie que le stockage n'est lu qu'une fois, au premier rendu.
useDebounce : attendre que l'utilisateur arrête de taper
Un champ de recherche ne devrait pas lancer une requête à chaque frappe. useDebounce renvoie une copie d'une valeur qui ne se met à jour qu'une fois que celle-ci a cessé de changer pendant un délai donné.
Tapez un mot rapidement : Typed suit chaque touche, Debounced rattrape une demi-seconde après votre arrêt, et la console affiche une seule recherche. Chaque frappe modifie value, donc React exécute le nettoyage (clearTimeout) avant de démarrer un nouveau minuteur. Remplacez 500 par 0 et chaque frappe affiche sa propre recherche.
useWindowWidth : s'abonner avec un nettoyage
Un hook qui écoute le navigateur doit retirer son écouteur quand le composant disparaît, sinon chaque montage en laisse un derrière lui.
Redimensionnez la fenêtre du navigateur et le nombre suit. Cliquez sur le bouton et la console montre le retrait de l'écouteur. Supprimez le return de l'effet, puis montez et démontez plusieurs fois : vous obtenez une ligne added à chaque fois et aucune removed, les anciens écouteurs s'accumulent donc. Pour des valeurs du navigateur comme celle-ci, useSyncExternalStore est le hook que React a conçu pour cette tâche, et c'est un bon candidat à appeler dans un hook personnalisé.
La logique est partagée, pas l'état
Deux composants qui appellent le même hook obtiennent deux copies séparées de tout ce qu'il contient. Cela surprend ceux qui s'attendent à ce qu'un hook fonctionne comme une variable globale.
Cliquez sur l'un ou l'autre bouton et seul ce compteur bouge. Si les deux doivent afficher le même nombre, déplacez l'état dans App et passez-le vers le bas, ou placez-le dans un contexte. Un hook personnalisé peut envelopper cet état partagé par commodité (useCart() qui lit un CartContext), mais le partage vient du contexte, pas du hook.
Quand écrire un hook personnalisé
Extrayez un hook quand le même groupe d'appels de hooks apparaît dans deux composants, ou quand la logique d'effet d'un composant est assez longue pour qu'un nom l'explique mieux (useOnlineStatus() se lit mieux que vingt lignes d'écouteurs). N'enveloppez pas un seul appel à useState dans un hook juste pour le renommer, et n'écrivez pas de hooks qui se contentent d'appeler useEffect pour définir un état à partir des props ; calculez plutôt cette valeur pendant le rendu.
Questions fréquentes
Qu'est-ce qu'un hook personnalisé dans React ?
Une fonction JavaScript dont le nom commence par use et qui appelle un ou plusieurs autres hooks. Elle regroupe une logique avec état (état, effets, refs) pour que n'importe quel composant puisse la réutiliser en appelant la fonction.
Les hooks personnalisés partagent-ils l'état entre composants ?
Non. Chaque appel à un hook personnalisé exécute ses propres appels à useState et useEffect, donc chaque composant obtient un état indépendant. Pour partager la même valeur entre composants, faites remonter l'état ou placez-le dans un contexte ou un store externe.
Pourquoi le nom d'un hook personnalisé doit-il commencer par use ?
Le nom indique aux règles de lint de React et aux autres développeurs que la fonction appelle des hooks, donc que les règles des hooks s'y appliquent : l'appeler au niveau supérieur, jamais dans une condition ni dans une boucle. Sans le préfixe, le linter ne peut pas vérifier les appels qu'elle contient.
Un hook personnalisé peut-il renvoyer du JSX ?
Il peut renvoyer n'importe quoi, mais s'il renvoie du JSX, il vaut généralement mieux l'écrire comme un composant. Les hooks personnalisés renvoient en général des valeurs et des fonctions, sous forme de tableau ([value, toggle]) ou d'objet ({ data, error, loading }).
Peut-on appeler un hook personnalisé dans useEffect ou un gestionnaire d'événement ?
Non. Un hook personnalisé reste un hook, il suit donc les règles des hooks : appelez-le au niveau supérieur d'un composant ou d'un autre hook personnalisé.