Conceptos Básicos de React Hooks
11 ejemplos para comenzar con React Hooks -- 7 básicos y 4 intermedios.
Prerrequisitos
Los Hooks se envían con React mismo -- no se requieren dependencias adicionales. Un proyecto React de TypeScript (Next.js, Vite, o CRA) es suficiente para ejecutar cada ejemplo a continuación.
# Si aún no tienes un proyecto:
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run devDos reglas se aplican a cada hook en esta página:
- Solo llama hooks en el nivel superior de un componente u otro hook -- nunca dentro de bucles, condiciones o funciones anidadas.
- Solo llama hooks desde componentes de función React u hooks personalizados, nunca desde funciones JavaScript normales.
Ejemplos Básicos
1. useState
Almacena un valor que provoca un re-renderizado cada vez que cambia.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount((c) => c + 1)}>
Contar: {count}
</button>
);
}useStatedevuelve un par[value, setter].- Llamar al setter programa un re-renderizado con el nuevo valor.
- Usa la forma de actualizador (
(c) => c + 1) cuando el nuevo valor depende del anterior -- evita cierres obsoletos. - El argumento a
useState(0)es el valor inicial, usado solo en el primer renderizado.
Relacionado: useState -- funciones actualizadoras, inicialización perezosa, agrupación | Tipificación de State -- tipificar formas de state complejas
2. useEffect
Ejecuta un efecto secundario después de renderizar -- suscribirse, cronometrar o tocar APIs que no son de React.
import { useEffect, useState } from "react";
function Clock() {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
return <p>{now.toLocaleTimeString()}</p>;
}- El efecto se ejecuta después de que el DOM se pinta, no durante el renderizado.
- Devuelve una función de limpieza para desmontar suscripciones, temporizadores u oyentes.
- El array de dependencias
[]significa "ejecutar una vez al montar"; listar variables significa "volver a ejecutar cuando cualquiera de ellas cambie". - No uses
useEffectpara datos que puedas obtener en un Componente de Servidor -- causa cascadas y parpadeos de carga.
Relacionado: useEffect -- limpieza, arrays de dependencias, errores comunes | SWR Basic Fetching -- prefiere esto para datos del lado del cliente
3. useRef
Mantiene un valor entre renderizados sin provocar un re-renderizado, o referencia un nodo del DOM.
import { useEffect, useRef } from "react";
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} placeholder="Enfocado automáticamente" />;
}useRefdevuelve un objeto{ current }que persiste entre renderizados.- Mutar
ref.currentno provoca un re-renderizado -- a diferencia del state. - Pasa la ref a la prop
refpara obtener un handle del elemento DOM subyacente. - Usos típicos: acceso al DOM, IDs de temporizadores que necesitas limpiar, almacenar el último valor de una prop o callback.
Relacionado: useRef -- patrones ref, reenvío, callback refs | Tipificación de Refs -- tipos ref para elementos e instancias
4. useContext
Lee un valor de contexto en cualquier lugar del árbol sin prop drilling.
import { createContext, useContext } from "react";
const ThemeContext = createContext<"light" | "dark">("light");
function ThemedLabel() {
const theme = useContext(ThemeContext);
return (
<span className={theme === "dark" ? "text-white" : "text-black"}>¡Hola!</span>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedLabel />
</ThemeContext.Provider>
);
}- Cada llamada a
useContextsuscribe el componente al contexto -- se re-renderiza cada vez que elvaluedel provider cambia. - El valor por defecto (
"light"aquí) se usa solo cuando no hay un provider coincidente arriba en el árbol. - Para state que se actualiza frecuentemente, una tienda dedicada evita re-renderizar todos los consumidores.
Relacionado: useContext -- providers, valores por defecto, división de contextos | Context Patterns -- cuándo y cómo dividir contextos | Context vs. Zustand -- elegir entre ellos
5. useReducer
Gestiona transiciones de state complejas con actualizaciones basadas en acciones.
import { useReducer } from "react";
type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return 0;
}
}
function Counter() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<span>{count}</span>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</div>
);
}- Usa
useReducercuando state tiene múltiples campos relacionados o transiciones complejas que abarcarían muchas llamadas auseState. - El reducer es una función pura -- la misma entrada, la misma salida, sin efectos secundarios.
- Tipificar acciones como una unión discriminada permite que TypeScript atrape llamadas a
dispatchinválidas. - Si usas más de 3-4 llamadas a
useStateen un componente, considera usaruseReduceren su lugar.
Relacionado: useReducer -- patrones de acciones, inicialización perezosa, state anidado | Discriminated Unions -- formas de acciones tipadas de forma segura
6. useMemo
Memoiza un cálculo caro para que se vuelva a ejecutar solo cuando sus entradas cambien.
import { useMemo, useState } from "react";
function ProductList({ products }: { products: { id: number; price: number }[] }) {
const [filter, setFilter] = useState("");
const total = useMemo(
() => products.reduce((sum, p) => sum + p.price, 0),
[products]
);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<p>Total: ${total}</p>
</div>
);
}useMemorecalcula el valor solo cuando las dependencias cambian, omitiendo el trabajo en re-renderizados no relacionados.- Úsalo para cálculos caros o para mantener la identidad referencial estable para props pasadas a componentes hijos memoizados.
- No envuelvas cada valor --
useMemoprematuro agrega sobrecarga de código sin ganancia de rendimiento. - Con el React Compiler (React 19+), muchas llamadas a
useMemose vuelven innecesarias -- el compilador memoiza automáticamente.
Relacionado: useMemo -- cuándo la memoización realmente ayuda | React Compiler -- auto-memoización en React 19 | Memoization -- patrones de rendimiento más amplios
7. useCallback
Memoiza una referencia de función para que permanezca estable entre renderizados.
import { useCallback, useState } from "react";
function SearchBox({ onSearch }: { onSearch: (q: string) => void }) {
return <input onChange={(e) => onSearch(e.target.value)} />;
}
function App() {
const [query, setQuery] = useState("");
const handleSearch = useCallback((q: string) => {
setQuery(q);
}, []);
return (
<>
<SearchBox onSearch={handleSearch} />
<p>Consulta: {query}</p>
</>
);
}useCallback(fn, deps)es equivalente auseMemo(() => fn, deps).- Útil cuando pasas un callback a un componente hijo memoizado (
React.memo) o cuando lo listas como una dependencia enuseEffect. - Sin memoización, cada re-renderizado crea una nueva referencia de función e invalida componentes hijos y efectos.
- Como
useMemo, evita el uso prematuro -- el React Compiler maneja la mayoría de casos en React 19.
Relacionado: useCallback -- patrones y tramapas | useMemo -- primitiva hermana | Re-renders -- cuándo la identidad del callback importa
Ejemplos Intermedios
8. Custom Hook
Extrae lógica con estado en una función reutilizable.
import { useEffect, useState } from "react";
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(() =>
typeof navigator === "undefined" ? true : navigator.onLine
);
useEffect(() => {
const on = () => setIsOnline(true);
const off = () => setIsOnline(false);
window.addEventListener("online", on);
window.addEventListener("offline", off);
return () => {
window.removeEventListener("online", on);
window.removeEventListener("offline", off);
};
}, []);
return isOnline;
}
function StatusBanner() {
const isOnline = useOnlineStatus();
return <p>Estás {isOnline ? "en línea" : "desconectado"}</p>;
}- Un hook personalizado es cualquier función cuyo nombre comienza con
usey puede llamar otros hooks. - Te permite compartir lógica entre componentes sin un componente envoltorio o componente de orden superior.
- Cada componente que llama el hook obtiene su propio state independiente -- nada se comparte entre sitios de llamada.
- Maneja SSR cuidadosamente -- las APIs solo del navegador (
navigator,window) necesitan guardias para el renderizado del servidor.
Relacionado: Custom Hooks -- reglas, pruebas, patrones | Custom Hooks Guide -- patrones más amplios | useToggle -- un hook personalizado del mundo real
9. useTransition
Marca una actualización de state como no urgente para que la UI permanezca receptiva.
import { useState, useTransition } from "react";
function FilterableList({ items }: { items: string[] }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
startTransition(() => {
setFiltered(items.filter((i) => i.includes(e.target.value)));
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <p>Actualizando...</p>}
<ul>
{filtered.map((i) => (
<li key={i}>{i}</li>
))}
</ul>
</div>
);
}- Las actualizaciones dentro de
startTransitionson baja prioridad -- React puede interrumpirlas si el usuario escribe de nuevo. isPendingte permite mostrar un indicador de carga sutil durante el trabajo diferido.- Úsalo para filtrado pesado, ordenamiento o actualizaciones de gráficos que de otro modo bloquearían la escritura.
- React 19 envuelve automáticamente envíos de
<form action={...}>en una transición -- no se necesitastartTransitionexplícito para acciones de formulario.
Relacionado: useTransition -- patrones y tramapas | useDeferredValue -- hook hermano para diferir un valor en lugar de una actualización
10. useActionState (React 19)
Impulsa una acción de formulario y rastreia su estado pendiente y resultado en una llamada.
"use client";
import { useActionState } from "react";
async function submitFeedback(_prev: string | null, formData: FormData) {
const message = formData.get("message") as string;
if (!message) return "Se requiere un mensaje.";
await fetch("/api/feedback", { method: "POST", body: formData });
return null;
}
function FeedbackForm() {
const [error, action, isPending] = useActionState(submitFeedback, null);
return (
<form action={action}>
<textarea name="message" />
{error && <p>{error}</p>}
<button type="submit" disabled={isPending}>
{isPending ? "Enviando..." : "Enviar"}
</button>
</form>
);
}- Devuelve
[state, action, isPending]-- conecta laactiondirectamente a<form action={action}>. - La función de acción recibe el state anterior y el
FormData; su valor de retorno se convierte en el siguiente state. - Combina con server actions para mejora progresiva -- el formulario funciona incluso antes de que JS se cargue.
- Renombrado de
useFormStatede React 18 -- la misma API, nuevo nombre.
Relacionado: useActionState -- API completa y patrones | Server Actions -- el lado del servidor del par | Server Action Forms -- patrones de formularios de extremo a extremo
11. use (React 19)
Lee una promise o contexto en línea -- sin .then, sin useEffect.
"use client";
import { Suspense, use } from "react";
interface User {
id: number;
name: string;
}
function UserCard({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <h2>{user.name}</h2>;
}
function UserPage({ userPromise }: { userPromise: Promise<User> }) {
return (
<Suspense fallback={<p>Cargando...</p>}>
<UserCard userPromise={userPromise} />
</Suspense>
);
}use(promise)suspende el componente hasta que la promise se resuelva -- el<Suspense>padre muestra el fallback.use(context)lee un valor de contexto y, a diferencia deuseContext, puede ser llamado condicionalmente dentro de bloquesif.- Crea la promise en un componente padre (a menudo un Componente de Servidor) y pásala hacia abajo -- no la crees dentro del componente de renderizado, o se recreará en cada renderizado.
- Combina con Server Components de transmisión para una historia de obtención de datos sin
useEffect.
Relacionado: use -- detalles completos de la API | Suspense -- el mecanismo de fallback | Server Components -- de donde usualmente se originan las promises