Un hook personalizado es una función cuyo nombre empieza con use y que llama a otros hooks. Escribes uno cuando dos componentes necesitan la misma lógica con estado: mueves las llamadas a useState y useEffect a la función, devuelves lo que necesita el componente y la llamas desde cada componente.
Ambos spoilers llaman a useToggle, y abrir uno deja el otro cerrado. Cada llamada ejecuta su propio useState, así que cada componente recibe su propio valor. Un hook personalizado comparte lógica, no estado.
Las reglas para escribir uno
- El nombre empieza con
usey una mayúscula:useToggle,useOnlineStatus. La regla de lint de los hooks usa el prefijo para encontrar las llamadas a hooks y comprobarlas. - Llama al menos a un hook. Una utilidad que no llama a ningún hook es una función normal; llámala
formatDate, nouseFormatDate, para que se pueda llamar en cualquier lugar, incluso dentro de condiciones. - Sigue las reglas de los hooks: llama a los hooks en su nivel superior, y llámalo solo desde componentes u otros hooks personalizados.
- Devuelve lo que resulte útil. Un array (
[on, toggle]) es práctico cuando quienes lo llaman renombran los valores; un objeto ({ data, loading, error }) se lee mejor con tres o más.
useLocalStorage: estado que sobrevive a una recarga
useState lo olvida todo al recargar. Este hook lee el valor inicial de localStorage y vuelve a escribir cada cambio.
La vista previa se ejecuta en un frame aislado donde el navegador bloquea localStorage, así que aquí verás el aviso en la consola y el hook vuelve a un estado normal. En una página normal el nombre sobrevive a una recarga. El try/catch no es solo para esta vista previa: los modos de navegación privada, las cookies bloqueadas y una cuota llena hacen que el almacenamiento lance errores también en apps reales. Pasar una función a useState significa que el almacenamiento se lee una sola vez, en el primer renderizado.
useDebounce: esperar a que el usuario deje de escribir
Una caja de búsqueda no debería lanzar una petición en cada pulsación. useDebounce devuelve una copia de un valor que solo se actualiza cuando lleva un tiempo determinado sin cambiar.
Escribe una palabra rápido: Typed sigue cada tecla, Debounced se pone al día medio segundo después de que paras, y la consola registra una sola búsqueda. Cada pulsación cambia value, así que React ejecuta la limpieza (clearTimeout) antes de iniciar un temporizador nuevo. Cambia 500 por 0 y cada pulsación registra su propia búsqueda.
useWindowWidth: suscribirse con limpieza
Un hook que escucha al navegador debe quitar su listener cuando el componente desaparece, o cada montaje deja uno atrás.
Cambia el tamaño de la ventana del navegador y el número lo sigue. Haz clic en el botón y la consola muestra que se quita el listener. Borra el return del efecto y luego monta y desmonta varias veces: obtienes una línea added cada vez y ninguna removed, así que los listeners viejos se acumulan. Para valores del navegador como este, useSyncExternalStore es el hook que React creó para esa tarea, y es bueno llamarlo dentro de un hook personalizado.
La lógica se comparte, el estado no
Dos componentes que llaman al mismo hook reciben dos copias separadas de todo lo que hay dentro. Eso sorprende a quienes esperan que un hook funcione como una variable global.
Haz clic en cualquiera de los botones y solo se mueve ese contador. Si ambos deben mostrar el mismo número, mueve el estado a App y pásalo hacia abajo, o ponlo en contexto. Un hook personalizado puede envolver ese estado compartido por comodidad (useCart() leyendo un CartContext), pero lo que comparte es el contexto, no el hook.
Cuándo escribir un hook personalizado
Extrae un hook cuando el mismo grupo de llamadas a hooks aparece en dos componentes, o cuando la lógica de efectos de un componente es tan larga que un nombre la explicaría mejor (useOnlineStatus() se lee mejor que veinte líneas de listeners). No envuelvas una sola llamada a useState en un hook solo para cambiarle el nombre, y no escribas hooks que solo llaman a useEffect para definir estado a partir de las props; calcula ese valor durante el renderizado.
Preguntas frecuentes
¿Qué es un hook personalizado en React?
Una función de JavaScript cuyo nombre empieza con use y que llama a uno o más hooks. Empaqueta lógica con estado (estado, efectos, refs) para que cualquier componente pueda reutilizarla llamando a la función.
¿Los hooks personalizados comparten estado entre componentes?
No. Cada llamada a un hook personalizado ejecuta sus propias llamadas a useState y useEffect, así que cada componente recibe un estado independiente. Para compartir el mismo valor entre componentes, eleva el estado o ponlo en contexto o en un store externo.
¿Por qué el nombre de un hook personalizado debe empezar con use?
El nombre les dice a las reglas de lint de React y a otros desarrolladores que la función llama a hooks, así que las reglas de los hooks se le aplican: llámalo en el nivel superior, nunca dentro de una condición o un bucle. Sin el prefijo, el linter no puede comprobar las llamadas que tiene dentro.
¿Un hook personalizado puede devolver JSX?
Puede devolver cualquier cosa, pero si devuelve JSX normalmente es mejor escribirlo como componente. Los hooks personalizados suelen devolver valores y funciones, como un array ([value, toggle]) o un objeto ({ data, error, loading }).
¿Puedo llamar a un hook personalizado dentro de useEffect o de un manejador de eventos?
No. Un hook personalizado sigue siendo un hook, así que sigue las reglas de los hooks: llámalo en el nivel superior de un componente o de otro hook personalizado.