Los React Hooks revolucionaron el desarrollo con React desde la versión 16.8. Permiten usar estado, efectos secundarios y otras características de React en componentes funcionales, sin necesidad de clases. En este tutorial aprenderás todos los hooks esenciales con ejemplos en TypeScript y casos de uso reales.
¿Qué son los hooks de React?
Los hooks son funciones especiales de React que empiezan por use. Solo pueden usarse en componentes funcionales o en otros custom hooks. Las dos reglas fundamentales:
- Solo llamar hooks en el nivel superior del componente (nunca dentro de if, for o funciones anidadas)
- Solo llamar hooks desde componentes de React o custom hooks
useState — Estado del componente
import { useState } from "react";
function Contador() {
const [cuenta, setCuenta] = useState<number>(0);
const [nombre, setNombre] = useState<string>("");
return (
<div>
<p>Cuenta: {cuenta}</p>
<button onClick={() => setCuenta(c => c + 1)}>+1</button>
<button onClick={() => setCuenta(c => c - 1)}>-1</button>
<input
value={nombre}
onChange={e => setNombre(e.target.value)}
placeholder="Tu nombre"
/>
{nombre && <p>Hola, {nombre}!</p>}
</div>
);
}
useEffect — Efectos secundarios
import { useState, useEffect } from "react";
interface Usuario { id: number; nombre: string; }
function UsuarioPerfil({ id }: { id: number }) {
const [usuario, setUsuario] = useState<Usuario | null>(null);
const [cargando, setCargando] = useState(true);
useEffect(() => {
let cancelado = false;
async function cargarUsuario() {
setCargando(true);
const res = await fetch(`/api/usuarios/${id}`);
const datos: Usuario = await res.json();
if (!cancelado) {
setUsuario(datos);
setCargando(false);
}
}
cargarUsuario();
// Funcion de limpieza: evita actualizaciones en componentes desmontados
return () => { cancelado = true; };
}, [id]); // Se re-ejecuta cuando cambia "id"
if (cargando) return <p>Cargando...</p>;
return <div>{usuario?.nombre}</div>;
}
useContext — Estado global sin prop drilling
import { createContext, useContext, useState } from "react";
interface TemaContextType { oscuro: boolean; toggleTema: () => void; }
const TemaContext = createContext<TemaContextType>({ oscuro: false, toggleTema: () => {} });
function TemaProvider({ children }: { children: React.ReactNode }) {
const [oscuro, setOscuro] = useState(false);
return (
<TemaContext.Provider value={{ oscuro, toggleTema: () => setOscuro(o => !o) }}>
{children}
</TemaContext.Provider>
);
}
function BotonTema() {
const { oscuro, toggleTema } = useContext(TemaContext);
return (
<button onClick={toggleTema}>
{oscuro ? "Modo claro" : "Modo oscuro"}
</button>
);
}
useReducer — Estado complejo
import { useReducer } from "react";
type Estado = { cuenta: number; historial: number[] };
type Accion = { tipo: "incrementar" | "decrementar" | "reset" };
function reducer(estado: Estado, accion: Accion): Estado {
switch (accion.tipo) {
case "incrementar":
return { cuenta: estado.cuenta + 1, historial: [...estado.historial, estado.cuenta + 1] };
case "decrementar":
return { cuenta: estado.cuenta - 1, historial: [...estado.historial, estado.cuenta - 1] };
case "reset":
return { cuenta: 0, historial: [] };
}
}
function ContadorAvanzado() {
const [estado, dispatch] = useReducer(reducer, { cuenta: 0, historial: [] });
return (
<div>
<p>Cuenta: {estado.cuenta}</p>
<button onClick={() => dispatch({ tipo: "incrementar" })}>+</button>
<button onClick={() => dispatch({ tipo: "decrementar" })}>-</button>
<button onClick={() => dispatch({ tipo: "reset" })}>Reset</button>
</div>
);
}
useMemo y useCallback — Optimización de rendimiento
import { useMemo, useCallback, useState } from "react";
function ListaFiltrada({ items }: { items: string[] }) {
const [filtro, setFiltro] = useState("");
// Solo recalcula cuando cambian items o filtro
const itemsFiltrados = useMemo(
() => items.filter(item => item.toLowerCase().includes(filtro.toLowerCase())),
[items, filtro]
);
// Referencia estable de la funcion (no se recrea en cada render)
const handleClick = useCallback((item: string) => {
console.log("Seleccionado:", item);
}, []);
return (
<div>
<input value={filtro} onChange={e => setFiltro(e.target.value)} placeholder="Filtrar..." />
{itemsFiltrados.map(item => (
<div key={item} onClick={() => handleClick(item)}>{item}</div>
))}
</div>
);
}
Custom Hook — Lógica reutilizable
// hooks/useFetch.ts
import { useState, useEffect } from "react";
function useFetch<T>(url: string) {
const [datos, setDatos] = useState<T | null>(null);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let activo = true;
setCargando(true);
fetch(url)
.then(r => r.json())
.then(d => { if (activo) { setDatos(d); setCargando(false); } })
.catch(e => { if (activo) { setError(e); setCargando(false); } });
return () => { activo = false; };
}, [url]);
return { datos, cargando, error };
}
// Uso en cualquier componente
interface Post { id: number; titulo: string; }
function Posts() {
const { datos, cargando, error } = useFetch<Post[]>("/api/posts");
if (cargando) return <p>Cargando...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{datos?.map(p => <li key={p.id}>{p.titulo}</li>)}</ul>;
}
Preguntas frecuentes sobre React Hooks
¿Cuándo usar useReducer en lugar de useState?
Usa useReducer cuando el estado es complejo con múltiples valores relacionados, cuando el siguiente estado depende del anterior de forma elaborada, o cuando la lógica de actualización tiene muchos casos. Una regla práctica: si tienes más de 3 useState relacionados entre sí o la lógica de actualización crece demasiado, es momento de migrar a useReducer.
¿Qué son los custom hooks y para qué sirven?
Un custom hook es una función cuyo nombre empieza por use y que puede llamar a otros hooks de React. Sirven para extraer lógica de estado reutilizable fuera de los componentes. Ejemplos comunes: useFetch para peticiones HTTP, useLocalStorage para persistencia, useWindowSize para dimensiones. El patrón permite compartir lógica compleja sin usar componentes de orden superior (HOC).
¿Cuál es la diferencia entre useMemo y useCallback?
useMemo memoriza el resultado de una computación costosa y solo la recalcula cuando cambian sus dependencias. useCallback memoriza una referencia a una función para que no se recree en cada render. La regla: usa useMemo para valores calculados pesados y useCallback para funciones que se pasan como props a componentes hijos memorizados con React.memo, evitando re-renders innecesarios.
¿Los hooks de React funcionan con TypeScript?
Sí, React y TypeScript son una combinación excelente. Los hooks tipados son muy expresivos: useState<string>("") infiere el tipo, useRef<HTMLInputElement>(null) da acceso tipado al DOM y useReducer acepta tipos genéricos para estado y acciones. Con @types/react instalado obtienes autocompletado completo y detección de errores en tiempo de escritura.
Conclusión
Los hooks de React son el corazón del desarrollo moderno con este framework. Domínalos y tu código será más limpio, reutilizable y testeable. Si usas TypeScript con React, revisa nuestro tutorial completo de TypeScript para sacar el máximo partido a la combinación.