¿Merece este hook su lugar? Diez preguntas de revisión para el código de React
Aprenda diez preguntas de revisión concretas que detectan estados redundantes, efectos obsoletos, bucles de renderizado, memorización innecesaria y componentes demasiado grandes en el código de React Hook.
Fuentes: Hooks
La mayoría de los errores en los React Hook no se parecen a errores en una solicitud de integración. Una dependencia faltante, un efecto que copia propiedades al estado o un useCallback alrededor de un manejador que nadie memoriza pasan la revisión, pero luego se manifiestan más tarde como pantallas obsoletas, renders adicionales o listeners acumulados. A continuación, diez errores frecuentes con los Hook se convierten en preguntas de revisión que puedes hacerle a cualquier componente, junto con el mecanismo detrás de cada uno y la forma más simple que suele requerir el código.
Preguntas sobre el estado
¿Se puede calcular este valor durante el renderizado?
Un patrón común consiste en mantener fullName en el estado y utilizar un efecto para llamar a setFullName(`${firstName} ${lastName}`) cada vez que cambia uno de los nombres. El componente se renderiza una vez con el valor antiguo, el efecto se ejecuta después de la pintura y el setter programa un segundo renderizado. Durante un frame, la interfaz de usuario es inconsistente y hay un componente adicional que debe mantenerse.
En su lugar, calcúlalo así: const fullName = `${firstName} ${lastName}`;. React vuelve a ejecutar el componente en cada renderizado, por lo que el valor siempre está sincronizado con sus entradas. Si algo puede derivarse de props o estado, entonces no es estado. Véase también por qué sincronizar el estado con useEffect tiene efectos negativos.
¿El componente almacena más estado del necesario?
El mismo error a mayor escala: un formulario con tres llamadas a useState para firstName, lastName y fullName. Cualquier cambio en el nombre debe reflejarse ahora en fullName, y cualquier ruta que lo olvide genera un error. Mantenga los dos campos de entrada independientes y obtenga el tercero a partir de ellos. Para cada useState, pregúntese si el valor podría reconstruirse a partir de los demás; de ser así, úselo como referencia y elimínelo.
¿Es necesario actualizar la pantalla al cambiar este valor?
El estado existe para controlar el renderizado. Un ID de temporizador, un nodo DOM, un valor anterior de una propiedad o una instancia de terceros (un mapa, un gráfico, un cliente de socket) por lo general no afectan lo que se dibuja, por lo que usar useState(null) para ellos implica que cada asignación provoca un renderizado innecesario. En su lugar, utilice const timerRef = useRef(null);: al escribir timerRef.current, este valor se mantiene entre renders sin necesidad de programar uno nuevo. La desventaja es que nunca debe leer un ref para decidir qué mostrar. Si la interfaz de usuario debe reflejar ese valor, al fin y al cabo se trata de estado.
Preguntas sobre los efectos
¿La matriz de dependencias enumera todo lo que lee el efecto?
Al pasar [] para que un efecto “se ejecute una sola vez”, como en useEffect(() => { fetchUser(userId); }, []);, se congela el valor de userId desde la primera renderización. Cuando cambia el prop, nada se vuelve a cargar y la interfaz muestra al usuario incorrecto. Escribir [userId] soluciona el problema.
El array no es un control de programación; sirve para indicar qué valores lee el efecto. Si el efecto se ejecuta con demasiada frecuencia, cambie lo que lee (integre una función dentro de él o dependa de un valor primitivo en lugar de de un objeto) en lugar de ocultar las dependencias. La regla de linting react-hooks/exhaustive-deps detecta casi todos estos casos, por lo que debe tratar sus advertencias como errores. Los efectos de carga también deben ignorar o cancelar las solicitudes obsoletas durante su limpieza cuando userId cambia rápidamente.
¿Puede este efecto activarse a sí mismo?
useEffect(() => { setCount(count + 1); }, [count]); es un bucle: la actualización cambia count, lo que hace que el efecto se ejecute nuevamente, indefinidamente. El actualizador funcional setCount(prev => prev + 1) lee el valor más reciente sin necesidad de incluir count en la cierre, por lo que esa dependencia puede eliminarse y el efecto solo se ejecuta al montar. Tenga en cuenta que aún así aumenta un valor por cada montaje, y que el Modo Estricto hace dos montajes en el entorno de desarrollo, por lo que localmente podría observarse un aumento de dos unidades.
La pregunta más relevante suele ser si la actualización realmente debe formar parte de un efecto. Las respuestas a las acciones del usuario se manejan en los manejadores de eventos; los valores derivados se calculan durante el renderizado. Los efectos sirven para sincronizarse con sistemas externos a React.
¿Deshace el efecto lo que ha configurado?
Un efecto que solo llama a window.addEventListener('resize', handleResize) agrega un nuevo oyente en cada montaje y nunca lo elimina. Devuelva una función de limpieza que llame a window.removeEventListener con el mismo manejador. React la ejecuta antes de que el efecto se ejecute nuevamente y al desmontar. Sin ella, se producen fugas de memoria, los manejadores se disparan varias veces y hay actualizaciones en componentes que ya no existen.
Una verificación rápida: cada add, connect o setInterval en un efecto necesita una función correspondiente de remove, disconnect o clearInterval en su valor de retorno. La auditoría de los diez errores comunes de useEffect aborda más modos de fallo en los efectos.
Preguntas sobre la memorización
¿Es realmente costoso este cálculo?
Rodear una concatenación de cadenas con useMemo utilizando [user] como dependencia cuesta más de lo que ahorra. React sigue asignando la función de callback y el array, además de comparar las dependencias en cada renderizado, lo cual implica más trabajo que simplemente unir dos cadenas. El código resulta más difícil de leer, y si user es un objeto nuevo en cada renderizado, el memo se vuelve a calcular de todos modos.
Utilice la expresión directa. Recurra a useMemo solo para tareas realmente costosas, como ordenar o filtrar una lista grande, o cuando un hijo memorizado u otro Hook necesite una referencia a un objeto estable. Primero, mida el rendimiento con el Profilador de React DevTools.
¿Algo depende de la identidad de esta función?
useCallback mantiene estable la referencia de una función, lo cual solo es importante cuando algo compara referencias: un componente hijo envuelto en React.memo o la lista de dependencias de otro Hook. Envolver un manejador de clic que solo registra eventos, asociado a un button simple, no aporta beneficio alguno, ya que al DOM no le importa si la función es nueva. Escriba una función normal y añada useCallback cuando pueda identificar al componente que la necesita. El compilador de React puede aplicar dicha memorización automáticamente; consulte la documentación actual para conocer su estado en su configuración.
Preguntas sobre la estructura
¿Se llama cada Hook incondicionalmente, en el mismo orden?
Declarar useState solo dentro de if (isLoggedIn) viola las Reglas de Hooks. React asocia los Hooks según su posición en la lista de cada componente, no por nombre. Si se omite una llamada, cada Hook posterior leerá el espacio de su vecino, lo que genera valores incorrectos o un error en tiempo de ejecución debido al cambio en la cantidad de Hooks.
Llame a todos los Hooks en el nivel superior y luego cree ramificaciones: declare primero el estado user y, si el usuario no está conectado, devuelva null posteriormente. Lo mismo aplica a los bucles y las devoluciones anticipadas. Para conocer más detalles, consulte por qué existen las Reglas de Hooks.
¿Está este componente realizando demasiadas tareas?
Un componente que mezcla cientos de líneas de lógica de obtención de datos, estado y negocio con marcado es difícil de leer, probar y modificar. Extrae la lógica en un Hook personalizado. Un Hook useUserData(userId) gestiona el estado user y el efecto que llama a fetchUser(userId).then(setUser) cuando userId cambia, para luego devolver user. UserProfile se reduce a const user = useUserData(userId); más la renderización de user?.name.
Esto te brinda:
- Reutilización sin necesidad de copiar la lógica entre componentes.
- Pruebas para la lógica independientes del marcado.
- Componentes más cortos y fáciles de analizar.
- Un único lugar para modificar la obtención o almacenamiento en caché de datos.
Recuerda que cada llamante de un Hook personalizado tiene su propio estado: comparte la lógica, no los datos.
Puntos clave
- Los valores derivados son expresiones, no estado ni efectos.
- Mantén el estado al mínimo; los valores que nunca llegan a la pantalla se guardan en refs.
- Los arrays de dependencias describen qué lee un efecto, así que revisalos con honestidad.
- Cada efecto que configura algo debe deshacerlo posteriormente.
- Memoriza solo cuando una medición o un consumidor sensible a referencias lo justifique.
- Llama a los Hooks incondicionalmente, y traslada bloques de lógica grandes a Hooks personalizados.
La mayoría de estos problemas surgen al recurrir a un Hook por costumbre. Preguntarse “¿Realmente necesitamos este Hook aquí?” durante las revisiones es sencillo y evita largas sesiones de depuración.
Lecturas relacionadas
- Elegir la herramienta correcta de React: Derive, Handle, Fetch, Defer o Effect — Una guía de decisiones para reemplazar las llamadas reflexivas a useEffect por valores derivados, manejadores de eventos, una capa de datos, useTransition, useMemo medido y las API de React 19.
- Rendimiento del frontend: Del punto ciego en la revisión de código a la métrica del producto — Aprenda por qué pasar por una revisión de código no es suficiente, cuáles Core Web Vitals son realmente importantes y cómo medir y solucionar los problemas reales de rendimiento en React.