Los Hooks en React cambiaron por completo la forma de construir componentes modernos. Si antes era habitual asociar el estado, los ciclos de vida y buena parte de la lógica interna a los componentes de clase, hoy lo más común es trabajar con componentes funcionales y Hooks como useState, useEffect, useRef, useMemo, useCallback o useContext.
Pero aquí aparece una duda muy habitual: ¿qué son exactamente los Hooks en React y cómo se usan sin complicar demasiado el código?
Dicho de forma sencilla, los Hooks son funciones especiales que permiten “conectar” un componente con diferentes características de React, como el estado, los efectos secundarios, el contexto o la optimización de cálculos. Gracias a ellos, puedes escribir componentes más claros, reutilizables y fáciles de mantener.
Si estás empezando a trabajar con React, también puede ser útil complementar esta lectura con contenidos relacionados como TypeScript: primeros pasos o con artículos más concretos sobre navegación en React, como cómo utilizar React Router Hash Link para crear enlaces ancla en ReactJS.
En este artículo vamos a ver qué son los React Hooks, para qué sirven, cuáles son los más utilizados y cómo aplicarlos en tus componentes con ejemplos prácticos. La idea no es memorizar todos los Hooks disponibles, sino entender cuándo tiene sentido usar cada uno y qué errores conviene evitar.
Qué son los Hooks en React
Los Hooks son funciones especiales que permiten usar características de React dentro de componentes funcionales. Normalmente, sus nombres empiezan por use, como useState, useEffect, useRef, useContext, useMemo o useCallback.
Antes de los Hooks, muchas funcionalidades avanzadas de React estaban ligadas a los componentes de clase. Por ejemplo, si querías manejar estado interno o ejecutar lógica cuando un componente se montaba, actualizaba o desmontaba, era habitual recurrir a métodos como componentDidMount, componentDidUpdate o componentWillUnmount.
Con los Hooks, esa lógica puede escribirse dentro de componentes funcionales de una forma más directa.
import { useState } from "react";
function Contador() {
const [contador, setContador] = useState(0);
return (
<div>
<p>Has hecho clic {contador} veces.</p>
<button onClick={() => setContador(contador + 1)}>
Incrementar
</button>
</div>
);
}
En este caso, contador es el valor actual del estado y setContador es la función que permite actualizarlo. Cada vez que se pulsa el botón, React actualiza el estado y vuelve a renderizar el componente con el nuevo valor.
La clave está en entender que un Hook no es magia. Es una forma ordenada de decirle a React: “este componente necesita recordar algo”, “este componente necesita sincronizarse con algo externo” o “este componente necesita reutilizar una lógica determinada”.
Por qué son importantes los React Hooks
Los React Hooks son importantes porque ayudan a escribir componentes más claros, reutilizables y fáciles de mantener. En lugar de depender de clases para gestionar estado o lógica de ciclo de vida, puedes trabajar con funciones y añadirles comportamiento mediante Hooks.
Esto tiene varias ventajas. La primera es que el código suele ser más compacto. La segunda es que resulta más sencillo separar responsabilidades. Y la tercera es que se pueden crear Hooks personalizados para reutilizar lógica entre componentes sin duplicar código.
Imagina, por ejemplo, que varios componentes necesitan saber si el usuario está conectado a internet. En lugar de repetir la misma lógica en cada componente, podrías crear un Hook personalizado llamado useOnlineStatus.
Esto permite organizar mejor la lógica de una aplicación. En lugar de tener componentes enormes, llenos de condiciones, efectos y cálculos, puedes extraer partes de esa lógica a Hooks propios.
En proyectos frontend reales, esta capacidad de separar responsabilidades es especialmente útil. Si además combinas React con buenas prácticas de tipado, puedes mejorar mucho la mantenibilidad del código. Por eso, si estás profundizando en este ecosistema, puede interesarte revisar también el artículo sobre TypeScript: primeros pasos.
Reglas básicas para usar Hooks en React
Aunque los Hooks son muy útiles, tienen unas reglas importantes. No se pueden llamar en cualquier lugar del código.
La regla principal es que los Hooks deben llamarse en el nivel superior del componente o dentro de otros Hooks personalizados. No deberían llamarse dentro de bucles, condicionales o funciones anidadas, porque React necesita que el orden de ejecución de los Hooks sea estable entre renderizados.
No llames Hooks dentro de condicionales
Este ejemplo sería incorrecto:
function Perfil({ usuario }) {
if (usuario) {
const [activo, setActivo] = useState(true);
}
return <p>Perfil de usuario</p>;
}
El problema es que el Hook solo se ejecutaría si usuario existe. Si en un renderizado existe y en otro no, React perdería el orden esperado de los Hooks.
La forma correcta sería esta:
import { useState } from "react";
function Perfil({ usuario }) {
const [activo, setActivo] = useState(true);
if (!usuario) {
return <p>No hay usuario disponible.</p>;
}
return <p>Perfil activo: {activo ? "Sí" : "No"}</p>;
}
Aquí el Hook se llama siempre en el mismo lugar, antes de cualquier retorno condicional importante.
Usa Hooks solo en componentes o en otros Hooks
Tampoco deberías llamar Hooks desde funciones normales de JavaScript que no sean componentes ni Hooks personalizados.
Este ejemplo no sería correcto:
function calcularEstadoInicial() {
const [valor, setValor] = useState(0);
return valor;
}
En cambio, si quieres extraer esa lógica, puedes crear un Hook personalizado:
import { useState } from "react";
function useValorInicial() {
const [valor, setValor] = useState(0);
return [valor, setValor];
}
La diferencia es importante: un Hook personalizado también empieza por use y está pensado para encapsular lógica compatible con React.
Principales Hooks en React y cómo usarlos
React incluye varios Hooks integrados. No necesitas dominarlos todos desde el primer día, pero sí conviene conocer los más comunes.
useStateuseEffectuseRefuseContextuseMemouseCallback
Cada uno resuelve un tipo de problema diferente. La clave está en no usarlos por inercia, sino entender qué necesidad concreta cubre cada uno.
useState: manejar estado local en un componente
useState es probablemente el primer Hook que se aprende. Sirve para guardar valores que pueden cambiar con el tiempo y que afectan a lo que se muestra en pantalla.
import { useState } from "react";
function FormularioNombre() {
const [nombre, setNombre] = useState("");
return (
<form>
<label htmlFor="nombre">Nombre</label>
<input
id="nombre"
value={nombre}
onChange={(event) => setNombre(event.target.value)}
/>
<p>Hola, {nombre || "visitante"}.</p>
</form>
);
}
Aquí el estado nombre se actualiza cada vez que el usuario escribe en el campo. Este patrón es habitual en formularios controlados.
Cuándo usar useState
Usa useState cuando el componente necesite recordar información que cambia y que afecta a la interfaz.
- Un contador.
- El valor de un campo de formulario.
- Un estado de carga.
- Una pestaña activa.
- Un modal abierto o cerrado.
- Un mensaje de error.
- Una selección hecha por el usuario.
Sin embargo, no todo debe ir en estado. Si un valor se puede calcular a partir de props o de otro estado existente, muchas veces es mejor calcularlo directamente durante el renderizado.
const nombreCompleto = `${nombre} ${apellido}`;
Cuanto menos estado innecesario tenga un componente, más fácil será mantenerlo.
useEffect: sincronizar un componente con algo externo
useEffect es uno de los Hooks más importantes, pero también uno de los que más confusión genera.
Este Hook permite ejecutar lógica después del renderizado del componente. Es especialmente útil cuando necesitas sincronizar el componente con algo externo al propio flujo de React.
- Llamadas a una API.
- Suscripciones.
- Eventos del navegador.
- Temporizadores.
- Lectura o escritura en
localStorage. - Integración con librerías externas.
import { useEffect, useState } from "react";
function Usuarios() {
const [usuarios, setUsuarios] = useState([]);
const [cargando, setCargando] = useState(true);
useEffect(() => {
async function obtenerUsuarios() {
try {
const respuesta = await fetch("/api/usuarios");
const datos = await respuesta.json();
setUsuarios(datos);
} finally {
setCargando(false);
}
}
obtenerUsuarios();
}, []);
if (cargando) {
return <p>Cargando usuarios...</p>;
}
return (
<ul>
{usuarios.map((usuario) => (
<li key={usuario.id}>{usuario.nombre}</li>
))}
</ul>
);
}
El array de dependencias [] indica que el efecto se ejecuta después del primer renderizado. Si incluyes dependencias dentro de ese array, el efecto volverá a ejecutarse cuando cambien.
Cuidado: no siempre necesitas un useEffect
Uno de los errores más frecuentes es usar useEffect para todo.
Por ejemplo, este caso puede ser innecesario:
const [nombreCompleto, setNombreCompleto] = useState("");
useEffect(() => {
setNombreCompleto(`${nombre} ${apellido}`);
}, [nombre, apellido]);
Sería más simple escribir:
const nombreCompleto = `${nombre} ${apellido}`;
Este detalle parece pequeño, pero es importante. Usar menos efectos ayuda a que tus componentes sean más previsibles. Un useEffect debería responder a una sincronización real, no convertirse en el lugar donde se coloca cualquier lógica.
Si estás trabajando con datos persistentes del navegador, también puede interesarte leer el artículo sobre almacenamiento web con localStorage y sessionStorage, porque combina muy bien con algunos casos prácticos de useEffect.
useRef: guardar referencias sin provocar renderizados
useRef permite guardar un valor mutable que se mantiene entre renderizados, pero que no provoca un nuevo renderizado cuando cambia.
Uno de sus usos más habituales es acceder a un elemento del DOM:
import { useRef } from "react";
function Buscador() {
const inputRef = useRef(null);
function enfocarInput() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} type="search" placeholder="Buscar..." />
<button onClick={enfocarInput}>
Enfocar campo
</button>
</div>
);
}
En este ejemplo, inputRef permite acceder directamente al input para hacer foco cuando el usuario pulsa el botón.
También puedes usar useRef para guardar valores que necesitas conservar, pero que no deben actualizar la interfaz por sí mismos.
import { useRef } from "react";
function Temporizador() {
const timerRef = useRef(null);
function iniciar() {
timerRef.current = setInterval(() => {
console.log("Intervalo activo");
}, 1000);
}
function detener() {
clearInterval(timerRef.current);
}
return (
<div>
<button onClick={iniciar}>Iniciar</button>
<button onClick={detener}>Detener</button>
</div>
);
}
La diferencia con useState es importante: si el valor debe mostrarse en pantalla, probablemente necesitas estado. Si solo necesitas guardar una referencia interna, useRef puede ser mejor opción.
useContext: compartir datos sin pasar props manualmente
useContext permite leer datos de un contexto de React desde un componente.
Es útil para datos globales o semiglobales, como:
- El tema visual.
- El idioma.
- La sesión de usuario.
- Ciertas preferencias de la aplicación.
- Configuraciones compartidas.
import { createContext, useContext } from "react";
const ThemeContext = createContext("light");
function Boton() {
const theme = useContext(ThemeContext);
return (
<button className={theme === "dark" ? "btn-dark" : "btn-light"}>
Cambiar tema
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Boton />
</ThemeContext.Provider>
);
}
useContext evita el conocido prop drilling, es decir, pasar props por muchos niveles intermedios solo para que lleguen a un componente profundo.
Ahora bien, no conviene usar contexto para absolutamente todo. Si un dato solo lo necesitan uno o dos componentes cercanos, probablemente sea suficiente pasarlo por props.
Cuándo tiene sentido usar useContext
Tiene sentido usar useContext cuando muchos componentes necesitan acceder al mismo dato y pasarlo por props haría el código más incómodo.
Por ejemplo, en una aplicación con modo claro y oscuro, el contexto puede ser una buena opción. En cambio, si solo necesitas pasar un texto desde un componente padre a un hijo directo, usar contexto sería excesivo.
Hooks de rendimiento: useMemo y useCallback
Cuando una aplicación crece, puede aparecer la necesidad de optimizar ciertos cálculos o evitar renderizados innecesarios. Aquí entran Hooks como useMemo y useCallback.
No son Hooks que deban usarse en cada componente. De hecho, usarlos sin necesidad puede hacer que el código sea más difícil de leer. Pero cuando existe un problema real de rendimiento o una dependencia que conviene estabilizar, pueden ser muy útiles.
useMemo: memorizar cálculos costosos
useMemo permite almacenar el resultado de un cálculo entre renderizados. Si sus dependencias no cambian, React puede reutilizar el resultado anterior.
import { useMemo, useState } from "react";
function ListaFiltrada({ productos }) {
const [busqueda, setBusqueda] = useState("");
const productosFiltrados = useMemo(() => {
return productos.filter((producto) =>
producto.nombre.toLowerCase().includes(busqueda.toLowerCase())
);
}, [productos, busqueda]);
return (
<div>
<input
value={busqueda}
onChange={(event) => setBusqueda(event.target.value)}
placeholder="Buscar producto"
/>
<ul>
{productosFiltrados.map((producto) => (
<li key={producto.id}>{producto.nombre}</li>
))}
</ul>
</div>
);
}
Este Hook puede ser útil si el filtrado es pesado o si la lista es muy grande. Pero no deberías usar useMemo por costumbre. Si el cálculo es simple, añadirlo puede complicar el código sin aportar una mejora real.
Cuándo usar useMemo
Puedes valorar useMemo cuando:
- El cálculo es costoso.
- La lista de datos es grande.
- El componente se renderiza con frecuencia.
- El resultado calculado se pasa a componentes memorizados.
- Has detectado un problema real de rendimiento.
Si ninguna de estas situaciones se cumple, probablemente no lo necesitas.
useCallback: memorizar funciones
useCallback permite mantener la misma referencia de una función entre renderizados, siempre que sus dependencias no cambien.
import { useCallback, useState } from "react";
function Panel() {
const [abierto, setAbierto] = useState(false);
const alternarPanel = useCallback(() => {
setAbierto((valorActual) => !valorActual);
}, []);
return (
<section>
<button onClick={alternarPanel}>
{abierto ? "Cerrar" : "Abrir"} panel
</button>
{abierto && <p>Contenido del panel.</p>}
</section>
);
}
Este Hook suele ser útil cuando pasas funciones a componentes memorizados o cuando una función aparece como dependencia de otro Hook.
Aun así, conviene repetir la misma idea: no hace falta envolver cada función en useCallback. Primero escribe código claro. Después optimiza donde tenga sentido.
Crear Hooks personalizados
Una de las partes más interesantes de los Hooks en React es que puedes crear tus propios Hooks.
Esto permite extraer lógica repetida y reutilizarla en distintos componentes. También ayuda a separar responsabilidades y a reducir el tamaño de los componentes.
Por ejemplo, puedes crear un Hook para leer y actualizar un valor en localStorage:
import { useEffect, useState } from "react";
function useLocalStorage(clave, valorInicial) {
const [valor, setValor] = useState(() => {
const valorGuardado = window.localStorage.getItem(clave);
return valorGuardado ? JSON.parse(valorGuardado) : valorInicial;
});
useEffect(() => {
window.localStorage.setItem(clave, JSON.stringify(valor));
}, [clave, valor]);
return [valor, setValor];
}
Y usarlo así:
function Preferencias() {
const [tema, setTema] = useLocalStorage("tema", "light");
return (
<button onClick={() => setTema(tema === "light" ? "dark" : "light")}>
Tema actual: {tema}
</button>
);
}
La ventaja es evidente: el componente Preferencias no necesita saber todos los detalles de localStorage. Solo consume una API sencilla.
Si quieres profundizar en este tema desde el lado del navegador, puedes complementar este ejemplo con el artículo sobre cómo usar localStorage y sessionStorage en proyectos JavaScript.
Buenas prácticas al crear Hooks propios
Un Hook personalizado debería tener una responsabilidad clara. Si hace demasiadas cosas, puede volverse tan difícil de mantener como el componente original.
También conviene cuidar el nombre. Si usa otros Hooks internamente, debe empezar por use. Esto ayuda a React, a las herramientas de linting y a cualquier persona que lea el código.
Por ejemplo:
function useUsuarioActual() {
// Lógica relacionada con el usuario actual
}
Mucho mejor que:
function obtenerUsuarioActual() {
// Si aquí se usan Hooks, el nombre no comunica bien su intención
}
El nombre useUsuarioActual deja claro que no es una función cualquiera, sino un Hook personalizado.
Ejemplo práctico: componente con varios Hooks
Veamos un ejemplo sencillo que combina varios Hooks en un mismo componente.
import { useEffect, useMemo, useRef, useState } from "react";
function BuscadorDeArticulos({ articulos }) {
const [consulta, setConsulta] = useState("");
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
const articulosFiltrados = useMemo(() => {
return articulos.filter((articulo) =>
articulo.titulo.toLowerCase().includes(consulta.toLowerCase())
);
}, [articulos, consulta]);
return (
<section>
<h2>Buscar artículos</h2>
<input
ref={inputRef}
value={consulta}
onChange={(event) => setConsulta(event.target.value)}
placeholder="Escribe una palabra clave"
/>
<p>
Se encontraron {articulosFiltrados.length} resultados.
</p>
<ul>
{articulosFiltrados.map((articulo) => (
<li key={articulo.id}>{articulo.titulo}</li>
))}
</ul>
</section>
);
}
Aquí useState guarda la consulta de búsqueda, useRef permite enfocar el input al cargar el componente, useEffect ejecuta esa acción después del primer renderizado y useMemo evita recalcular la lista filtrada si no cambian los artículos ni la consulta.
Este ejemplo muestra una idea importante: los Hooks no compiten entre sí. Cada uno cumple una función concreta. El objetivo es elegir el Hook que mejor encaja con el problema.
Errores comunes al usar Hooks en React
Los Hooks son muy expresivos, pero también pueden generar problemas si se usan sin criterio.
Usar useEffect para lógica que pertenece a eventos
Si algo ocurre como respuesta directa a una acción del usuario, muchas veces debería estar en el manejador del evento, no en un useEffect.
Por ejemplo, si quieres enviar un formulario al hacer clic en un botón, lo normal es hacerlo en onSubmit, no detectar con un efecto que un estado ha cambiado y entonces enviar los datos.
Ignorar las dependencias de useEffect
El array de dependencias no es un adorno. Si un efecto usa valores externos, como props o estado, esas dependencias deberían estar reflejadas.
Un error habitual es eliminar dependencias para “evitar que el efecto se dispare”. Eso puede ocultar bugs y provocar que el componente trabaje con valores antiguos.
Guardar demasiado estado
No todo necesita useState. Si puedes calcular un valor a partir de otro, calcúlalo. Si puedes pasarlo por props, pásalo. Si puedes resolverlo en el renderizado, evita añadir un estado extra.
Cuanto más estado tenga un componente, más posibilidades hay de que distintas piezas de información se desincronicen.
Optimizar antes de tiempo
useMemo y useCallback son útiles, pero no deberían convertirse en una decoración automática.
Antes de añadirlos, conviene preguntarse:
- ¿Hay realmente un problema de rendimiento?
- ¿El cálculo es costoso?
- ¿Estoy pasando esta función a un componente memorizado?
- ¿El código se entiende mejor o peor después de añadir este Hook?
En muchas aplicaciones, la claridad del código aporta más valor que una microoptimización prematura.
Cómo decidir qué Hook necesitas
Cuando estés trabajando con componentes de React, puedes hacerte algunas preguntas para elegir mejor.
¿Necesito recordar un valor que afecta a la interfaz?
Probablemente necesitas useState.
Ejemplos: un modal abierto, una pestaña activa, el valor de un formulario o un mensaje de error.
¿Necesito sincronizar el componente con algo externo?
Probablemente necesitas useEffect.
Ejemplos: escuchar eventos del navegador, conectar con una API, actualizar el título del documento o trabajar con una librería externa.
¿Necesito acceder a un elemento del DOM o guardar un valor mutable?
Probablemente necesitas useRef.
Ejemplos: enfocar un input, guardar un temporizador o conservar una referencia que no debe provocar renderizados.
¿Necesito compartir datos entre muchos componentes?
Probablemente necesitas useContext, aunque conviene valorar si unas props serían suficientes.
¿Tengo un cálculo pesado o una función que provoca renderizados innecesarios?
Puede que necesites useMemo o useCallback, pero solo si hay un motivo claro.
Hooks en React y arquitectura de componentes
Los Hooks no solo sirven para resolver detalles técnicos. También influyen en cómo piensas la arquitectura de tus componentes.
Un componente debería tener una responsabilidad clara. Si empieza a tener demasiados estados, demasiados efectos y demasiadas funciones internas, quizá sea una señal de que necesita dividirse.
Puedes extraer:
- Componentes más pequeños.
- Hooks personalizados.
- Funciones auxiliares.
- Lógica de datos.
- Lógica de presentación.
Esta separación ayuda a que el código sea más legible. También facilita el testing, la reutilización y la evolución del proyecto.
Por ejemplo, si estás construyendo una navegación compleja en React, puedes separar la lógica de rutas, anclas o scroll en componentes específicos. En ese caso, puede interesarte revisar también cómo utilizar React Router Hash Link para crear enlaces ancla en ReactJS.
La idea de fondo es sencilla: un buen componente no debería intentar hacerlo todo. Los Hooks te ayudan a encapsular comportamiento, pero también debes usarlos con intención.
FAQs sobre Hooks en React
¿Qué son los Hooks en React?
Los Hooks en React son funciones especiales que permiten usar características de React dentro de componentes funcionales. Por ejemplo, useState permite gestionar estado, useEffect permite sincronizar el componente con sistemas externos y useContext permite consumir datos de un contexto.
¿Cuál es la diferencia entre useState y useRef?
useState guarda valores que, al cambiar, provocan un nuevo renderizado del componente. useRef, en cambio, guarda una referencia mutable que se mantiene entre renderizados, pero su actualización no vuelve a renderizar la interfaz. Por eso useState es mejor para datos visibles y useRef para referencias internas.
¿Cuándo debería crear un Hook personalizado?
Deberías crear un Hook personalizado cuando detectes lógica repetida entre varios componentes o cuando quieras separar una responsabilidad concreta para mejorar la legibilidad. Por ejemplo, puedes crear Hooks para gestionar formularios, leer datos de localStorage, detectar el estado de conexión o encapsular llamadas a una API.
Elegir bien tus Hooks también es diseñar mejor tus componentes
Los Hooks en React no son simplemente una sintaxis moderna. Son una forma de organizar la lógica de los componentes de manera más clara, declarativa y reutilizable.
Aprender qué son los Hooks en React y cómo usarlos implica algo más que memorizar useState o useEffect. Significa entender cuándo un componente necesita estado, cuándo debe sincronizarse con algo externo, cuándo una referencia mutable es suficiente y cuándo merece la pena extraer lógica a un Hook personalizado.
La mejor recomendación es empezar por lo simple. Usa useState cuando necesites estado. Usa useEffect solo cuando exista una sincronización real con algo externo. Usa useRef cuando necesites conservar una referencia sin afectar al renderizado. Y deja useMemo y useCallback para casos donde realmente aporten claridad o rendimiento.
En React, escribir buenos componentes no consiste en usar todos los Hooks posibles, sino en elegir bien. Porque un componente bien diseñado no solo funciona: también se entiende, se mantiene y se puede hacer crecer sin que cada cambio se convierta en un pequeño incendio.