Hooks de React: cómo un componente recuerda entre renders
Última revisión:
React- Conceptos básicos
- Estado
Respuesta corta
Los hooks son funciones que se enganchan a React para que un componente de función recuerde valores entre renders, lea contexto o se sincronice con algo externo, cosas que antes solo podían hacer las clases.
Se llaman siempre en el mismo orden y en el nivel superior del componente, porque React los identifica por ese orden.
En esta página
Explicación
Por qué existen
Dos definiciones rápidas para partir. Renderizar es que React vuelva a ejecutar la función del componente para obtener su UI. El estado es un dato del componente que, al cambiar, provoca un nuevo render.
El problema es que la función se ejecuta completa cada vez, así que una variable local nace de nuevo en cada render: no puede recordar nada. Los hooks son la forma de pedirle a React que guarde ese valor por mí y, en general, de engancharme a sus funcionalidades (estado, contexto, refs, efectos) desde un componente de función.
Antes de React 16.8 (2019), los componentes de función no tenían estado ni ciclo de vida: para eso había que escribir una clase. Y las clases traían tres problemas:
- La lógica quedaba repartida por momento, no por tema. Suscribirse a algo iba en
componentDidMounty desuscribirse, encomponentWillUnmount, mezclado con otras lógicas en los mismos métodos. - Reutilizar lógica era incómodo. Había que usar patrones como los HOCs o las render props, que llenaban el árbol de componentes envoltorio.
thisconfundía. Había que acordarse de enlazar los métodos o usar funciones flecha.
Con los hooks, la lógica de un tema queda junta y se reutiliza como una función más.
Los hooks principales
| Hook | Para qué sirve |
|---|---|
useState |
Guardar un valor que, al cambiar, vuelve a renderizar el componente |
useReducer |
Lo mismo, pero con la lógica de actualización centralizada en un reducer |
useEffect |
Sincronizar el componente con algo externo después del render |
useContext |
Leer un valor compartido sin pasarlo por props nivel a nivel |
useRef |
Guardar un valor que persiste sin provocar renders, o una referencia a un elemento del DOM |
useMemo |
Cachear un valor calculado entre renders |
useCallback |
Cachear una función entre renders |
Las reglas y su porqué
Las reglas de los hooks son dos:
- Solo en el nivel superior del componente. Nunca dentro de un
if, un bucle, una función anidada ni después de unreturnanticipado. La razón es la clave: React no conoce los nombres de mis variables. Guarda los valores de los hooks de cada componente en orden y, en cada render, los entrega según el orden en que se llaman: el primeruseStaterecibe el primer valor, el segundo recibe el segundo, y así. Si unifse salta un hook en algún render, todos los que vienen después se corren una posición y reciben un valor que no es el suyo. - Solo desde componentes de función o desde custom hooks. No en funciones comunes, componentes de clase ni event handlers. La razón: React asocia cada hook al componente que se está renderizando en ese momento. Un custom hook se ejecuta dentro de ese render, porque lo llama el componente. Una función común o un event handler, en cambio, puede correr fuera de cualquier render, y React no tendría a qué componente atar el valor.
No hace falta vigilarlo a mano: el plugin eslint-plugin-react-hooks detecta estos errores.
Custom hooks
Un custom hook es una función propia cuyo nombre empieza con use y que llama a otros hooks. Sirve para extraer lógica con estado y reutilizarla en varios componentes, como hace useOnlineStatus en el código de abajo. El prefijo no es decorativo: le indica a React y al linter que ahí se aplican las reglas.
El punto que más importa: un custom hook reutiliza la lógica, no el estado. Cada componente que lo llama tiene su propia copia independiente. Si dos componentes necesitan compartir el mismo dato, eso se resuelve con contexto o con un store.
Código
Un indicador de conexión: hay que escuchar los eventos online y offline del navegador y dejar de escucharlos cuando el componente desaparece. Primero con una clase y después con hooks.
Antes (clase)
import { Component } from 'react';
type State = { isOnline: boolean };
export class OnlineBadge extends Component<object, State> { state: State = { isOnline: navigator.onLine };
handleOnline = () => this.setState({ isOnline: true }); // flecha para no perder this handleOffline = () => this.setState({ isOnline: false });
componentDidMount() { window.addEventListener('online', this.handleOnline); window.addEventListener('offline', this.handleOffline); }
// la limpieza de esa misma lógica vive en otro método componentWillUnmount() { window.removeEventListener('online', this.handleOnline); window.removeEventListener('offline', this.handleOffline); }
render() { return <span>{this.state.isOnline ? 'En línea' : 'Sin conexión'}</span>; }}Después (hooks)
import { useEffect, useState } from 'react';
// custom hook: empieza con use y llama a otros hooksexport function useOnlineStatus(): boolean { const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { // limpieza: la misma lógica, en el mismo lugar window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []);
return isOnline;}
export function OnlineBadge() { const isOnline = useOnlineStatus(); // cada componente tiene su propio estado return <span>{isOnline ? 'En línea' : 'Sin conexión'}</span>;}Trade-offs
- Lo que ganas. La lógica queda agrupada por tema y no por momento del ciclo de vida, se reutiliza sin componente envoltorio y hay menos código repetitivo, como se ve en el ejemplo.
- Lo que cuesta. Hay que pensar en términos de render: un callback puede quedarse con valores de un render anterior y los efectos dependen de declarar bien sus dependencias. Lo desarrollo en la entrada de
useEffectvsuseMemo. - Reglas estrictas, pero vigiladas. Al principio parecen arbitrarias; una vez que entiendes que React los identifica por orden, tienen sentido. Y el linter las controla por ti.
- Las clases no desaparecieron. Siguen funcionando y no están deprecadas, y los error boundaries todavía necesitan una clase. No hace falta migrar código que ya funciona.
- El error típico. Llamar un hook dentro de un
if, o creer que dos componentes que usan el mismo custom hook comparten el estado.
Entradas relacionadas
React
¿Qué es un componente? Lo aplicamos en React y Blazor
Un componente es una pieza encapsulada y reutilizable de la interfaz: recibe datos desde afuera, puede tener estado propio y describe cómo debe verse la UI para esos datos. Renderizarlo es ejecutarlo para obtener esa descripción; luego el framework la compara con la anterior y actualiza en el DOM solo lo que cambió.
GeneralReact
useEffect() vs useMemo(): ¿cuándo y cómo se elige cada uno?
useEffect() sincroniza el componente con algo externo después del render y no produce un valor para mostrar. useMemo() calcula un valor durante el render y lo guarda en caché. Si un dato se puede derivar de props o estado, se calcula en el render, nunca con un efecto.
React