Saltar al contenido
Under the Hood
Available in Spanish only

React

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 componentDidMount y desuscribirse, en componentWillUnmount, 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.
  • this confundí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:

  1. Solo en el nivel superior del componente. Nunca dentro de un if, un bucle, una función anidada ni después de un return anticipado. 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 primer useState recibe el primer valor, el segundo recibe el segundo, y así. Si un if se 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.
  2. 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)

OnlineBadge.tsx
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)

OnlineBadge.tsx
import { useEffect, useState } from 'react';
// custom hook: empieza con use y llama a otros hooks
export 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 useEffect vs useMemo.
  • 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ó.

    General
  • React

    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