Reutilizar lógica con Hooks personalizados
React viene con varios Hooks incorporados comouseState,useContext y useEffect. A veces, desearás que hubiera un Hook para algún propósito más específico: por ejemplo, para obtener datos, para rastrear si el usuario está en línea o para conectarse a una sala de chat. Puede que no encuentres estos Hooks en React, pero puedes crear tus propios Hooks para las necesidades de tu aplicación.
Aprenderás
- Qué son los Hooks personalizados y cómo escribir los tuyos
- Cómo reutilizar lógica entre componentes
- Cómo nombrar y estructurar tus Hooks personalizados
- Cuándo y por qué extraer Hooks personalizados
Hooks personalizados: Compartir lógica entre componentes
Imagina que estás desarrollando una aplicación que depende en gran medida de la red (como la mayoría de las aplicaciones). Quieres advertir al usuario si su conexión de red se ha desconectado accidentalmente mientras usaba tu aplicación. ¿Cómo lo harías? Parece que necesitarás dos cosas en tu componente:
- Un estado que rastrea si la red está en línea.
- Un Efecto que se suscribe a los eventos globalesonline y offline, y actualiza ese estado.
Esto mantendrá tu componentesincronizadocon el estado de la red. Podrías comenzar con algo como esto:
Intenta encender y apagar tu red, y observa cómo estaStatusBarse actualiza en respuesta a tus acciones.
Ahora imagina quetambiénquieres usar la misma lógica en un componente diferente. Quieres implementar un botón Guardar que se deshabilite y muestre "Reconectando..." en lugar de "Guardar" mientras la red esté desconectada.
Para empezar, puedes copiar y pegar el estadoisOnliney el Efecto enSaveButton:
Verifica que, si apagas la red, el botón cambiará su apariencia.
Estos dos componentes funcionan bien, pero la duplicación de lógica entre ellos es desafortunada. Parece que, aunque tienen unaapariencia visual diferente,quieres reutilizar la lógica entre ellos.
Extraer tu propio Hook personalizado de un componente
Imagina por un momento que, similar auseState y useEffect, hubiera un Hook incorporado llamadouseOnlineStatus. Entonces ambos componentes podrían simplificarse y podrías eliminar la duplicación entre ellos:
Aunque no existe tal Hook incorporado, puedes escribirlo tú mismo. Declara una función llamadauseOnlineStatusy mueve todo el código duplicado a ella desde los componentes que escribiste antes:
Al final de la función, devuelveisOnline. Esto permite que tus componentes lean ese valor:
Verifica que al activar y desactivar la red se actualicen ambos componentes.
Ahora tus componentes no tienen tanta lógica repetitiva.Más importante aún, el código dentro de ellos describelo que quieren hacer(¡usar el estado de conexión!) en lugar decómo hacerlo(suscribiéndose a los eventos del navegador).
Cuando extraes lógica en Hooks personalizados, puedes ocultar los detalles complejos de cómo interactúas con un sistema externo o una API del navegador. El código de tus componentes expresa tu intención, no la implementación.
Los nombres de los Hooks siempre empiezan conuse
Las aplicaciones de React se construyen a partir de componentes. Los componentes se construyen a partir de Hooks, ya sean incorporados o personalizados. Es probable que uses a menudo Hooks personalizados creados por otros, pero ocasionalmente podrías escribir uno tú mismo.
Debes seguir estas convenciones de nomenclatura:
- Los nombres de los componentes de React deben empezar con mayúscula,como
StatusBarySaveButton. Los componentes de React también deben devolver algo que React sepa mostrar, como un fragmento de JSX. - Los nombres de los Hooks deben empezar con
useseguido de una mayúscula,comouseState(incorporado) ouseOnlineStatus(personalizado, como antes en la página). Los Hooks pueden devolver valores arbitrarios.
Esta convención garantiza que siempre puedes mirar un componente y saber dónde podrían "esconderse" su estado, Efectos y otras características de React. Por ejemplo, si ves una llamada a la funcióngetColor()dentro de tu componente, puedes estar seguro de que no puede contener estado de React porque su nombre no empieza conuse. Sin embargo, una llamada a función comouseOnlineStatus()probablemente contendrá llamadas a otros Hooks dentro.
Nota
Si tu linter estáconfigurado para React,hará cumplir esta convención de nomenclatura. Desplázate hacia arriba al sandbox anterior y renombrauseOnlineStatus a getOnlineStatus. Observa que el linter no te permitirá llamar auseState o useEffectdentro de él. ¡Solo los Hooks y los componentes pueden llamar a otros Hooks!
Los Hooks personalizados te permiten compartir lógica con estado, no el estado en sí
En el ejemplo anterior, cuando encendiste y apagaste la red, ambos componentes se actualizaron juntos. Sin embargo, es incorrecto pensar que una única variable de estadoisOnlinese comparte entre ellos. Mira este código:
Funciona de la misma manera que antes de extraer la duplicación:
¡Estas son dos variables de estado y Efectos completamente independientes! Ocurrió que tenían el mismo valor al mismo tiempo porque las sincronizaste con el mismo valor externo (si la red está encendida).
Para ilustrar esto mejor, necesitaremos un ejemplo diferente. Considera este componenteForm:
Hay cierta lógica repetitiva para cada campo del formulario:
- Hay una pieza de estado (
firstNameylastName). - Hay un controlador de cambio (
handleFirstNameChangeyhandleLastNameChange). - Hay una pieza de JSX que especifica los atributos
valueyonChangepara ese input.
Puedes extraer la lógica repetitiva en este Hook personalizadouseFormInput:
Observa que solo declaraunavariable de estado llamadavalue.
Sin embargo, el componenteFormllama auseFormInputdos veces:
¡Por eso funciona como declarar dos variables de estado separadas!
Los Hooks personalizados te permiten compartirlógica con estadopero no elestado en sí.Cada llamada a un Hook es completamente independiente de cualquier otra llamada al mismo Hook.Por eso los dos sandboxes anteriores son completamente equivalentes. Si lo deseas, desplázate hacia arriba y compáralos. El comportamiento antes y después de extraer un Hook personalizado es idéntico.
Cuando necesites compartir el estado en sí entre múltiples componentes,elévalo y pásalo hacia abajoen su lugar.
Pasar valores reactivos entre Hooks
El código dentro de tus Hooks personalizados se volverá a ejecutar durante cada nuevo renderizado de tu componente. Por eso, al igual que los componentes, los Hooks personalizadosdeben ser puros.¡Piensa en el código de los Hooks personalizados como parte del cuerpo de tu componente!
Debido a que los Hooks personalizados se vuelven a renderizar junto con tu componente, siempre reciben las props y el estado más recientes. Para ver qué significa esto, considera este ejemplo de sala de chat. Cambia la URL del servidor o la sala de chat:
Cuando cambiasserverUrl o roomId, el Efecto“reacciona” a tus cambiosy se resincroniza. Puedes notarlo por los mensajes de la consola que indican que el chat se reconecta cada vez que cambias las dependencias de tu Efecto.
Ahora mueve el código del Efecto a un Hook personalizado:
Esto permite que tu componenteChatRoomllame a tu Hook personalizado sin preocuparse de cómo funciona internamente:
¡Esto se ve mucho más simple! (Pero hace lo mismo).
Observa que la lógicasigue respondiendoa los cambios de props y estado. Intenta editar la URL del servidor o la sala seleccionada:
Observa cómo estás tomando el valor de retorno de un Hook:
y pasándolo como entrada a otro Hook:
Cada vez que tu componenteChatRoomse vuelve a renderizar, pasa los últimos valores deroomId y serverUrla tu Hook. Por eso tu Efecto se reconecta al chat cada vez que sus valores son diferentes después de un nuevo renderizado. (Si alguna vez has trabajado con software de procesamiento de audio o video, encadenar Hooks de esta manera podría recordarte encadenar efectos visuales o de audio. Es como si la salida deuseState"alimentara" la entrada deluseChatRoom).
Pasar manejadores de eventos a Hooks personalizados
A medida que comiences a usaruseChatRoomen más componentes, es posible que quieras permitir que los componentes personalicen su comportamiento. Por ejemplo, actualmente, la lógica de qué hacer cuando llega un mensaje está codificada dentro del Hook:
Supongamos que quieres mover esta lógica de vuelta a tu componente:
Para que esto funcione, cambia tu Hook personalizado para que acepteonReceiveMessagecomo una de sus opciones nombradas:
Esto funcionará, pero hay una mejora más que puedes hacer cuando tu Hook personalizado acepta manejadores de eventos.
Agregar una dependencia enonReceiveMessageno es ideal porque hará que el chat se reconecte cada vez que el componente se vuelva a renderizar.Envuelve este manejador de eventos en un Evento de Efecto para eliminarlo de las dependencias:
Ahora el chat no se reconectará cada vez que el componente
Observa cómo ya no necesitas sabercómouseChatRoomfunciona para poder usarlo. Podrías añadirlo a cualquier otro componente, pasar cualquier otra opción, y funcionaría de la misma manera. Ese es el poder de los Hooks personalizados.
Cuándo usar Hooks personalizados
No necesitas extraer un Hook personalizado por cada pequeño fragmento de código duplicado. Un poco de duplicación está bien. Por ejemplo, extraer un HookuseFormInputpara envolver una única llamada auseStatecomo antes probablemente no sea necesario.
Sin embargo, cada vez que escribas un Efecto, considera si sería más claro también envolverlo en un Hook personalizado.No deberías necesitar Efectos con mucha frecuencia,así que si estás escribiendo uno, significa que necesitas "salir de React" para sincronizar con algún sistema externo o hacer algo para lo que React no tiene una API incorporada. Envolverlo en un Hook personalizado te permite comunicar con precisión tu intención y cómo fluyen los datos a través de él.
Por ejemplo, considera un componenteShippingFormque muestra dos menús desplegables: uno muestra la lista de ciudades y otro muestra la lista de áreas en la ciudad seleccionada. Podrías comenzar con un código similar a este:
Aunque este código es bastante repetitivo,es correcto mantener estos Efectos separados entre sí.Sincronizan dos cosas diferentes, por lo que no deberías fusionarlos en un solo Efecto. En su lugar, puedes simplificar el componenteShippingFormanterior extrayendo la lógica común entre ellos en tu propio HookuseData:
Ahora puedes reemplazar ambos Efectos en los componentesShippingFormcon llamadas auseData:
Extraer un Hook personalizado hace explícito el flujo de datos. Proporcionas laurly obtienes losdata. Al "ocultar" tu Efecto dentro deuseData, también evitas que alguien que trabaje en el componenteShippingForm añada dependencias innecesarias a él. Con el
Los Hooks personalizados te ayudan a migrar a mejores patrones
Los Efectos son una"salida de emergencia": los usas cuando necesitas "salir de React" y cuando no hay una mejor solución incorporada para tu caso de uso. Con el tiempo, el objetivo del equipo de React es reducir la cantidad de Efectos en tu aplicación al mínimo, proporcionando soluciones más específicas para problemas más específicos. Envolver tus Efectos en Hooks personalizados facilita la actualización de tu código cuando estas soluciones estén disponibles.
Volvamos a este ejemplo:
En el ejemplo anterior,useOnlineStatusse implementa con un par deuseState y useEffect.Sin embargo, esta no es la mejor solución posible. Hay varios casos extremos que no considera. Por ejemplo, asume que cuando el componente se monta,isOnlineya estrue, pero esto puede ser incorrecto si la red ya se desconectó. Puedes usar la API del navegadornavigator.onLinepara verificar eso, pero usarla directamente no funcionaría en el servidor para generar el HTML inicial. En resumen, este código podría mejorarse.
React incluye una API dedicada llamadauseSyncExternalStoreque se encarga de todos estos problemas por ti. Aquí está tu HookuseOnlineStatus, reescrito para aprovechar esta nueva API:
Observa cómono necesitaste cambiar ninguno de los componentespara realizar esta migración:
Esta es otra razón por la cual envolver Efectos en Hooks personalizados suele ser beneficioso:
- Haces que el flujo de datos hacia y desde tus Efectos sea muy explícito.
- Permites que tus componentes se centren en la intención en lugar de en la implementación exacta de tus Efectos.
- Cuando React añade nuevas características, puedes eliminar esos Efectos sin cambiar ninguno de tus componentes.
Similar a unsistema de diseño,puede resultarte útil comenzar a extraer expresiones comunes de los componentes de tu aplicación en Hooks personalizados. Esto mantendrá el código de tus componentes centrado en la intención y te permitirá evitar escribir Efectos crudos con frecuencia. Muchos Hooks personalizados excelentes son mantenidos por la comunidad de React.
Hay más de una manera de hacerlo
Digamos que quieres implementar una animación de fundido de entradadesde cerousando la API del navegadorrequestAnimationFrame. Podrías comenzar con un Efecto que configure un bucle de animación. Durante cada fotograma de la animación, podrías cambiar la opacidad del nodo del DOM quemantienes en una refhasta que alcance1. Tu código podría comenzar así:
Para que el componente sea más legible, podrías extraer la lógica en un Hook personalizadouseFadeIn:
Podrías mantener el código deuseFadeIntal cual, pero también podrías refactorizarlo más. Por ejemplo, podrías extraer la lógica para configurar el bucle de animación fuera deuseFadeInen un Hook personalizadouseAnimationLoop:
Sin embargo, notenías quehacerlo. Como con las funciones regulares, al final tú decides dónde trazar los límites entre las diferentes partes de tu código. También podrías adoptar un enfoque muy diferente. En lugar de mantener la lógica en el Efecto, podrías mover la mayor parte de la lógica imperativa dentro de unaclase de JavaScript:
Los Efectos te permiten conectar React con sistemas externos. Cuanta más coordinación se necesite entre Efectos (por ejemplo, para encadenar múltiples animaciones), más sentido tiene extraer esa lógica completamente fuera de los Efectos y Hooks,como en el sandbox anterior. Entonces, el código que extraístese convierteen el "sistema externo". Esto permite que tus Efectos se mantengan simples porque solo necesitan enviar mensajes al sistema que has movido fuera de React.
Los ejemplos anteriores asumen que la lógica de aparición gradual necesita escribirse en JavaScript. Sin embargo, esta animación particular de aparición gradual es tanto más simple como mucho más eficiente de implementar con una simpleAnimación CSS:
¡A veces, ni siquiera necesitas un Hook!
Recapitulación
- Los Hooks personalizados te permiten compartir lógica entre componentes.
- Los Hooks personalizados deben nombrarse comenzando con
useseguido de una letra mayúscula. - Los Hooks personalizados solo comparten lógica con estado, no el estado en sí.
- Puedes pasar valores reactivos de un Hook a otro, y se mantienen actualizados.
- Todos los Hooks se vuelven a ejecutar cada vez que tu componente se vuelve a renderizar.
- El código de tus Hooks personalizados debe ser puro, como el código de tu componente.
- Envuelve los controladores de eventos recibidos por Hooks personalizados en Eventos de Efecto.
- No crees Hooks personalizados como
useMount. Mantén su propósito específico. - Depende de ti cómo y dónde elegir los límites de tu código.
Try out some challenges
Challenge 1 of 5:Extract a useCounter Hook #
This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called useCounter. Your goal is to make the Counter component implementation look exactly like this:
export default function Counter() {
const count = useCounter();
return <h1>Seconds passed: {count}</h1>;
}You’ll need to write your custom Hook in useCounter.js and import it into the App.js file.
