Los hooks de React son funciones como useState, useEffect y useRef que dan a los componentes de función estado, efectos secundarios y refs. Esta chuleta cubre todos los hooks integrados de React 19.3 con su propósito en una línea, un ejemplo en vivo y su error más común, además de 12 hooks personalizados que puedes copiar.
En el momento de escribir esto (octubre de 2026), la última versión de React es la 19.3. La tabla recoge todos los hooks de la referencia oficial de hooks de React, más los tres hooks de React 19 documentados en sus propias páginas (use, useOptimistic y el hook de React DOM useFormStatus). Los hooks marcados con React 18 funcionan en React 18 y 19. Los marcados con React 19 no existen en React 18. Cada hook de React 18 de abajo tiene un ejemplo en vivo en el que puedes hacer clic; los de React 19 se muestran solo como código, porque esta página funciona con React 18.
| Hook | Grupo | Úsalo para | Desde |
|---|---|---|---|
| useState | Estado | guardar un valor que vuelve a renderizar el componente cuando cambia | React 18 |
| useReducer | Estado | gestionar estado con muchas actualizaciones relacionadas mediante una función reductora | React 18 |
| useContext | Contexto | leer un valor del provider más cercano por encima | React 18 |
| useRef | Ref | guardar un valor mutable o un nodo del DOM que no provoca renderizados | React 18 |
| useImperativeHandle | Ref | elegir qué métodos puede llamar un padre a través de una ref | React 18 |
| useEffect | Efecto | sincronizar con algo externo a React después del pintado | React 18 |
| useLayoutEffect | Efecto | medir el layout antes de que el navegador pinte | React 18 |
| useInsertionEffect | Efecto | insertar reglas de estilo antes de que se ejecuten los efectos de layout (bibliotecas CSS-in-JS) | React 18 |
| useEffectEvent | Efecto | leer las props más recientes dentro de un efecto sin volver a ejecutarlo | React 19.2 |
| useMemo | Rendimiento | cachear un cálculo costoso entre renderizados | React 18 |
| useCallback | Rendimiento | cachear una función para que los hijos memorizados no se vuelvan a renderizar | React 18 |
| useTransition | Rendimiento | marcar una actualización de estado como no urgente y obtener un indicador de pendiente | React 18 |
| useDeferredValue | Rendimiento | dejar que una parte lenta de la UI vaya por detrás de la escritura rápida | React 18 |
| useId | Otros | generar ID únicos y estables para etiquetas y atributos ARIA | React 18 |
| useSyncExternalStore | Otros | suscribirse a un store que vive fuera de React | React 18 |
| useDebugValue | Otros | etiquetar un hook personalizado en React DevTools | React 18 |
| useActionState | Otros | seguir el resultado y el estado pendiente de una acción de formulario | React 19 |
| useOptimistic | Otros | mostrar el resultado esperado de una acción antes de que termine | React 19 |
| use | API | leer una promesa o un contexto, incluso dentro de condiciones | React 19 |
| useFormStatus | React DOM | leer el estado pendiente del formulario padre | React 19 |
Los hooks siguen dos reglas: llámalos solo en el nivel superior de un componente o de un hook personalizado (nunca dentro de bucles, condiciones o funciones anidadas), y llámalos solo desde componentes de función de React o desde otros hooks. use es la única excepción a la primera regla: puede llamarse dentro de bloques if y de bucles. El paquete eslint-plugin-react-hooks comprueba ambas reglas por ti.
useStateQué hace: Guarda un valor para este componente. Llamar al setter programa un nuevo renderizado con el valor nuevo.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount((c) => c - 1)}>-1</button>
<span>{count}</span>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</>
);
}
Error común: Escribir setCount(count + 1) tres veces en un mismo clic y esperar +3. Las tres leen el mismo count de este renderizado, así que obtienes +1. Pasa una función de actualización, setCount(c => c + 1), cuando el siguiente valor dependa del anterior. El botón "+3 (updater)" de arriba hace exactamente eso. Otro clásico: mutar un objeto o array en su sitio (user.name = "x"; setUser(user)). React compara por referencia, ve el mismo objeto y se salta el renderizado. Crea un objeto nuevo en su lugar: setUser({ ...user, name: "x" }).
useReducerQué hace: Lleva la lógica de actualización a una única función pura que recibe el estado actual y una acción y devuelve el siguiente estado. Es útil cuando varios valores cambian a la vez.
type Action = { type: "add"; text: string } | { type: "toggle"; id: number };
function reducer(state: Todo[], action: Action): Todo[] {
switch (action.type) {
case "add":
return [...state, { id: Date.now(), text: action.text, done: false }];
case "toggle":
return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
}
}
const [todos, dispatch] = useReducer(reducer, []);
dispatch({ type: "add", text: "Ship it" });
Error común: Hacer efectos secundarios dentro del reducer (peticiones, escribir en localStorage, generar ID aleatorios a partir de un temporizador del que dependes después). En desarrollo, con Strict Mode, React invoca dos veces a los reducers para destapar el código impuro. Mantén el reducer puro y pon los efectos secundarios en manejadores de eventos o en efectos.
useContextQué hace: Lee el valor del provider correspondiente más cercano por encima de este componente y vuelve a renderizar cuando ese valor cambia.
const ThemeContext = createContext<"light" | "dark">("light");
function App() {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Badge() {
const theme = useContext(ThemeContext);
return <span className={theme}>Current theme: {theme}</span>;
}
Error común: Pasar un objeto literal nuevo como valor del provider en cada renderizado, por ejemplo value={{ user, setUser }}. Cada renderizado crea un objeto nuevo, así que todos los consumidores se vuelven a renderizar aunque nada haya cambiado. Envuelve el valor en useMemo, o separa en dos contextos los valores que cambian poco y los que cambian a menudo. Comprueba también que el provider esté por encima del componente: llamar a useContext en el mismo componente que renderiza el provider devuelve el valor por defecto.
useRefQué hace: Te da una caja (ref.current) que sobrevive a los renderizados. Cambiarla no provoca un renderizado. Úsala para nodos del DOM, ID de temporizadores y otros valores que la pantalla no muestra.
const inputRef = useRef<HTMLInputElement>(null);
const clicks = useRef(0);
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
<button onClick={() => { clicks.current += 1; }}>Count silently</button>
Error común: Mostrar ref.current en el JSX y esperar que se actualice. Las refs no provocan renderizados, así que la pantalla solo se pone al día cuando otra cosa vuelve a renderizar (prueba el botón "Añadir a la ref" y luego "Volver a renderizar" arriba). Si el valor debe aparecer en pantalla, su sitio es el estado. Evita también leer o escribir refs durante el renderizado, salvo para una inicialización diferida única.
useImperativeHandleQué hace: Personaliza lo que recibe un padre cuando pone una ref en tu componente, de modo que expones unos pocos métodos con nombre en lugar del nodo del DOM completo.
type Handle = { shake: () => void; clear: () => void };
// React 18: wrap in forwardRef. In React 19 you can accept ref as a normal prop.
const FancyInput = forwardRef<Handle, Props>(function FancyInput(props, ref) {
const el = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
shake() { /* animate */ el.current?.focus(); },
clear() { if (el.current) el.current.value = ""; },
}), []);
return <input ref={el} />;
});
// parent
const ref = useRef<Handle>(null);
<FancyInput ref={ref} />
<button onClick={() => ref.current?.shake()}>Shake</button>
Error común: Usarlo para controlar cosas que se pueden expresar con props. Si un padre invoca a ref.current.open() para mostrar un modal, una prop isOpen suele ser más sencilla y fácil de seguir. Reserva los handles imperativos para foco, scroll, reproducir/pausar y animaciones. Olvidar el array de dependencias (el tercer argumento) también recrea el handle en cada renderizado.
useEffectQué hace: Ejecuta código después de que React haya actualizado la pantalla, para mantener un componente sincronizado con algo externo a React: un temporizador, una suscripción, una petición de red, un widget que no es de React. Devuelve una función de limpieza para deshacerlo.
useEffect(() => {
if (!running) return;
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // runs before the next effect and on unmount
}, [running]);
Error común: Usar un efecto para calcular algo que podrías calcular durante el renderizado, como useEffect(() => setFullName(first + " " + last), [first, last]). Eso cuesta un renderizado extra y puede mostrar valores desactualizados durante un fotograma. Escribe const fullName = first + " " + last en su lugar. El segundo fallo más común es olvidar la limpieza: en desarrollo, con Strict Mode, React monta, desmonta y vuelve a montar cada componente una vez, así que un efecto sin limpieza se suscribirá o iniciará un temporizador dos veces. Esa doble ejecución es una pista, no un fallo de React.
useLayoutEffectQué hace: Tiene la misma forma que useEffect, pero se ejecuta después de que React cambie el DOM y antes de que el navegador pinte. Úsalo para medir un elemento y ajustar el layout para que los usuarios nunca vean la posición incorrecta.
const boxRef = useRef<HTMLSpanElement>(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
setWidth(boxRef.current!.getBoundingClientRect().width);
}, [text]);
Error común: Recurrir a él por defecto. Bloquea el pintado, así que el trabajo lento en su interior hace que la página parezca atascada. Usa useEffect salvo que veas un parpadeo que solo arregla una medición previa al pintado (tooltips, posicionamiento de popovers, tamaño automático). Además no hace nada durante el renderizado en el servidor, lo que produce un aviso en algunas configuraciones.
useInsertionEffectQué hace: Se ejecuta antes que cualquier efecto de layout para que una biblioteca pueda insertar reglas <style> antes de que nada lea el layout. Existe para quienes desarrollan bibliotecas CSS-in-JS.
function useScopedRule(selector: string, rule: string) {
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = `${selector} { ${rule} }`;
document.head.appendChild(style);
return () => style.remove();
}, [selector, rule]);
}
Error común: Usarlo en el código de la aplicación o intentar actualizar el estado desde él. No puedes programar actualizaciones dentro de él y las refs todavía no están asignadas. Si no estás escribiendo una biblioteca de estilos, casi seguro que quieres useEffect o useLayoutEffect.
useEffectEventQué hace: React 19.2 y posteriores. Envuelve una función para que un efecto pueda llamarla y vea siempre las props y el estado más recientes, sin tener que listar esos valores como dependencias. Úsalo para la parte de "evento" de un efecto, como registrar algo o mostrar una notificación con el tema actual.
// React 19.2+ only. Not run on this page.
import { useEffect, useEffectEvent } from "react";
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification("Connected", theme); // always the latest theme
});
useEffect(() => {
const conn = createConnection(roomId);
conn.on("connected", () => onConnected());
conn.connect();
return () => conn.disconnect();
}, [roomId]); // theme is not a dependency, and onConnected must not be listed
}
Error común: Poner el evento de efecto en el array de dependencias, llamarlo durante el renderizado o pasarlo a otros componentes. La documentación de React dice que los eventos de efecto se llamen solo desde dentro de efectos (u otros eventos de efecto), y su regla de lint avisa cuando incluyes uno como dependencia. En React 18 el sustituto habitual es el patrón de "ref más reciente" que usa useInterval más abajo en esta página.
useMemoQué hace: Cachea el resultado de un cálculo y solo lo recalcula cuando cambian sus dependencias.
const filtered = useMemo(
() => items.filter((item) => item.includes(query)),
[items, query],
);
Error común: Envolverlo todo en él. Memorizar trabajo barato como a + b o un map corto añade código y coste de comparación sin ganar nada. Mide primero (React DevTools Profiler) y memoriza cálculos lentos, valores que se pasan a hijos con memo y valores usados como dependencias de efectos. Una trampa relacionada: una dependencia que es un objeto nuevo en cada renderizado ({ sort: "asc" }) hace que el memo se recalcule siempre.
useCallbackQué hace: Devuelve la misma instancia de función entre renderizados hasta que cambia una dependencia. Importa cuando la función se pasa a un hijo envuelto en memo o se usa como dependencia de un efecto.
const handleAdd = useCallback(() => setCount((c) => c + 1), []);
const Child = memo(function Child({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>Add</button>;
});
<Child onClick={handleAdd} />
Error común: Usar useCallback cuando el hijo no está memorizado. Sin memo en el hijo, este se vuelve a renderizar con su padre de todos modos, así que la función estable no cambia nada. La demo en vivo muestra los dos hijos: solo el que recibe la función cacheada se salta renderizados. Los closures obsoletos son la otra trampa: si el callback lee count pero la lista de dependencias está vacía, sigue viendo el primer valor. Usa una función de actualización o incluye la dependencia.
useTransitionQué hace: Marca una actualización de estado como no urgente. React mantiene la pantalla actual receptiva mientras prepara la nueva y te da un indicador isPending para mostrar que algo está cargando.
const [isPending, startTransition] = useTransition();
function selectTab(next: string) {
startTransition(() => {
setTab(next);
});
}
Error común: Envolver en una transición el propio estado de un campo de texto. Los inputs controlados deben actualizarse con urgencia, o escribir parecerá roto. Mantén normal el estado del input y mueve a la transición la actualización lenta y derivada (o usa useDeferredValue). En React 18 la función que se pasa a startTransition debe ser síncrona; React 19 también acepta funciones async.
useDeferredValueQué hace: Te da una copia de un valor a la que se le permite ir con retraso. Las partes rápidas de la UI usan el valor real; una parte lenta se renderiza con el diferido y se pone al día cuando el navegador está libre.
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<SlowResults query={deferredQuery} />
</div>
Error común: Olvidar envolver el hijo lento en memo. Si el hijo se vuelve a renderizar con el padre en cada pulsación de todos modos, diferir su prop no ayuda. Tampoco es un debounce: no espera un tiempo fijo ni reduce las peticiones de red. Para hacer menos peticiones, usa debounce (mira useDebounce más abajo).
useIdQué hace: Genera un ID único dentro de la app e idéntico en el servidor y en el cliente, para htmlFor, aria-describedby y atributos similares.
function Field({ label }: { label: string }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
Error común: Usarlo para crear las keys de una lista. Las keys deben salir de tus datos (un ID de la base de datos), no de un hook. Evita también Math.random() para los ID de elementos en apps renderizadas en el servidor: el servidor y el navegador generan números distintos y la hidratación falla, que es justo el problema que resuelve useId.
useSyncExternalStoreQué hace: Suscribe un componente a una fuente de datos que no pertenece a React (una API del navegador, un store global) y lee una instantánea que se mantiene coherente durante el renderizado concurrente.
function subscribe(callback: () => void) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
const online = useSyncExternalStore(
subscribe,
() => navigator.onLine, // client snapshot
() => true, // server snapshot
);
Error común: Definir subscribe en línea dentro del componente. Una función nueva en cada renderizado hace que React cancele la suscripción y vuelva a suscribirse cada vez. Defínela fuera del componente o envuélvela en useCallback. La función de instantánea también debe devolver el mismo valor cuando nada ha cambiado: devolver un objeto o array nuevo en cada llamada provoca un bucle infinito de renderizados.
useDebugValueQué hace: Añade una etiqueta a un hook personalizado en React DevTools. No tiene ningún efecto en lo que ven los usuarios.
function useOnlineStatus() {
const online = useSyncExternalStore(subscribe, () => navigator.onLine);
useDebugValue(online ? "Online" : "Offline");
return online;
}
Error común: Añadirlo a cada hook personalizado o hacer en él un formateo costoso. Úsalo en hooks compartidos cuyo estado sea difícil de leer en DevTools, y pasa un formateador como segundo argumento si la etiqueta es costosa de construir: useDebugValue(date, d => d.toISOString()).
use, useActionState, useOptimistic, useFormStatus) no los exporta React 18. Esta página funciona con React 18, así que se muestran como código que puedes copiar en un proyecto de React 19, sin demos en vivo. useEffectEvent, explicado más arriba, necesita React 19.2.useActionStateQué hace: Envuelve una acción de formulario y te da su último resultado más un indicador de pendiente. La acción recibe el estado anterior y el FormData enviado.
// React 19+
import { useActionState } from "react";
async function subscribe(prev: { error?: string; ok?: boolean }, formData: FormData) {
const email = String(formData.get("email") ?? "");
if (!email.includes("@")) return { error: "Enter a valid email" };
await saveEmail(email);
return { ok: true };
}
function Newsletter() {
const [state, formAction, isPending] = useActionState(subscribe, {});
return (
<form action={formAction}>
<input name="email" type="email" />
<button disabled={isPending}>{isPending ? "Saving..." : "Subscribe"}</button>
{state.error && <p role="alert">{state.error}</p>}
{state.ok && <p>Thanks, you are subscribed.</p>}
</form>
);
}
Error común: Olvidar que el primer argumento de la acción es el estado anterior. Escribir async function subscribe(formData) significa que formData es en realidad el estado antiguo y .get lanza un error. Ten en cuenta también que durante un tiempo se llamó useFormState (en react-dom) en el periodo canary de React 19; la documentación actual usa useActionState de react.
useOptimisticQué hace: Muestra una versión temporal y optimista del estado mientras se ejecuta una acción async, y vuelve al estado real cuando termina.
// React 19+
import { useOptimistic } from "react";
function Thread({ messages, send }: Props) {
const [optimistic, addOptimistic] = useOptimistic(
messages,
(current, text: string) => [...current, { text, sending: true }],
);
async function action(formData: FormData) {
const text = String(formData.get("text"));
addOptimistic(text);
await send(text); // parent updates `messages` when this resolves
}
return (
<form action={action}>
{optimistic.map((m, i) => <p key={i}>{m.text}{m.sending && " (sending)"}</p>)}
<input name="text" />
</form>
);
}
Error común: Llamar al setter optimista fuera de una acción o transición. React lo espera dentro de una acción de formulario o de startTransition; en otro sitio el valor optimista aparece y desaparece al instante (React avisa en desarrollo). Y gestiona siempre el fallo: si send lanza un error, muestra un mensaje para que el contenido no desaparezca sin más.
useQué hace: Lee el valor de una promesa (suspendiendo hasta que se resuelve) o de un contexto. A diferencia de otros hooks, puede llamarse dentro de condiciones y bucles.
// React 19+
import { use, Suspense } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // suspends until resolved
return comments.map((c) => <p key={c.id}>{c.text}</p>);
}
function Page({ commentsPromise }) {
return (
<Suspense fallback={<p>Loading comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// Conditional context read, which useContext cannot do:
function Heading({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext);
return <h1 className={theme}>Hello</h1>;
}
Error común: Crear la promesa dentro del componente que invoca a use, como use(fetch(url)). Cada renderizado crea una promesa nueva, así que el componente queda suspendido para siempre. Crea la promesa una sola vez (en un server component, un loader, una caché o un padre) y pásala hacia abajo. use tampoco puede ir dentro de try/catch; gestiona los errores con un error boundary.
useFormStatusQué hace: Se importa de react-dom. Lee el estado del <form> padre: pending, data, method y action.
// React 19+
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>{pending ? "Sending..." : "Send"}</button>;
}
function ContactForm() {
return (
<form action={sendMessage}>
<textarea name="message" />
<SubmitButton />
</form>
);
}
Error común: Llamarlo en el mismo componente que renderiza el <form>. Solo informa sobre un formulario situado por encima del componente que lo invoca, así que en el propio componente del formulario pending siempre es false. Mueve el botón a su propio componente, como arriba.
useEffect, useLayoutEffect, useMemo, useCallback y useImperativeHandle reciben una lista de valores como último argumento. React compara cada elemento con el valor del renderizado anterior usando Object.is. Si alguno cambió, el efecto se vuelve a ejecutar (o el memo se recalcula). Esa única regla explica todos los casos:
| Escribes | El efecto se ejecuta | Uso típico |
|---|---|---|
| useEffect(fn) | después de cada renderizado | poco frecuente; suele indicar que algo debería calcularse durante el renderizado |
| useEffect(fn, []) | una vez tras el montaje (dos veces en desarrollo con Strict Mode), limpieza al desmontar | suscripciones y configuración que no usan props ni estado |
| useEffect(fn, [a, b]) | tras el montaje y después cada vez que cambian a o b | cargar datos por ID, sincronizar con una prop |
Haz clic en los botones: cambiar b vuelve a ejecutar solo el efecto sin array, cambiar a vuelve a ejecutar ese y también el efecto [a], y el efecto [] se queda quieto tras el montaje.
eslint-plugin-react-hooks y deja que exhaustive-deps te diga qué falta.{ id } o () => {} nunca son iguales al anterior según Object.is, así que vuelven a ejecutar el efecto en cada renderizado. Muévelos dentro del efecto, sácalos del componente o memorízalos.[user.id] es más estable que [user] cuando solo importa el ID.setCount(c => c + 1) no lee count, así que no hace falta incluir count.useEffectEvent en React 19.2+, o una ref que mantengas actualizada en React 18.// Stale closure: logs 0 forever
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // count is missing
// Fixed: re-subscribe when count changes
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, [count]);
// Fetch with cleanup so a slow old response cannot overwrite a newer one
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((r) => r.json())
.then(setUser)
.catch((err) => { if (err.name !== "AbortError") setError(err); });
return () => controller.abort();
}, [userId]);
Un hook personalizado es una función cuyo nombre empieza por use y que invoca otros hooks. Permite que varios componentes compartan lógica con estado; cada componente que lo invoca obtiene su propio estado independiente. Los hooks de abajo están escritos para esta página en TypeScript, funcionan en React 18 y 19 y no tienen dependencias. Cada uno termina con una nota sobre cuándo no usarlo.
useDebounceDevuelve un valor que solo se actualiza cuando la entrada lleva delay milisegundos sin cambiar. Va bien para cuadros de búsqueda que llaman a una API.
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = window.setTimeout(() => setDebounced(value), delay);
return () => window.clearTimeout(id);
}, [value, delay]);
return debounced;
}
// const debouncedQuery = useDebounce(query, 400);
// useEffect(() => { search(debouncedQuery); }, [debouncedQuery]);
Cuándo no usarlo: Cuando solo necesitas que la UI siga respondiendo mientras renderiza una lista grande. Eso es un problema de renderizado, y useDeferredValue lo resuelve sin un retraso artificial. Evítalo también en validaciones de formularios que deberían sentirse instantáneas.
useLocalStorageFunciona como useState, pero guarda el valor en localStorage y lo restaura en la siguiente visita.
import { useEffect, useState } from "react";
export function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try {
const raw = window.localStorage.getItem(key);
return raw === null ? initial : (JSON.parse(raw) as T);
} catch {
return initial; // private mode, blocked storage or bad JSON
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
/* storage full or blocked: keep the in-memory value */
}
}, [key, value]);
return [value, setValue] as const;
}
Cuándo no usarlo: Para cualquier dato sensible (tokens, datos personales), ya que cualquier script de la página puede leer localStorage. Tampoco en páginas renderizadas en el servidor sin cuidado: el servidor no tiene window, así que lee el almacenamiento tras el montaje para evitar un desajuste de hidratación. No sincroniza entre pestañas; añade un listener del evento storage si lo necesitas.
useMediaQueryDevuelve true mientras se cumple una media query de CSS y se actualiza cuando cambia.
import { useCallback, useSyncExternalStore } from "react";
export function useMediaQuery(query: string): boolean {
const subscribe = useCallback((onChange: () => void) => {
const mql = window.matchMedia(query);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return useSyncExternalStore(
subscribe,
() => window.matchMedia(query).matches,
() => false, // server snapshot
);
}
// const isDesktop = useMediaQuery("(min-width: 1024px)");
// const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
Cuándo no usarlo: Para cambios de layout que CSS puede hacer por sí solo. Ocultar o reorganizar elementos con breakpoints de Tailwind (md:hidden) funciona antes de que cargue JavaScript y nunca parpadea. Usa el hook cuando tengas que renderizar componentes distintos o evitar por completo cargar algo.
useToggleUn booleano con una función toggle estable, para menús, modales y secciones desplegables.
import { useCallback, useState } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle, setOn] as const;
}
// const [open, toggleOpen, setOpen] = useToggle();
Cuándo no usarlo: Cuando el estado tiene más de dos valores (cerrado, abriéndose, abierto, error). Usa una unión de strings con useState o useReducer en lugar de apilar varios toggles.
usePreviousDevuelve el valor del renderizado anterior, práctico para mostrar un cambio ("sube desde 12") o reaccionar a una transición.
import { useEffect, useRef } from "react";
export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Cuándo no usarlo: Para decidir si actualizar el estado. La documentación de React muestra un patrón más limpio para eso: guarda el valor anterior en el estado y compara durante el renderizado. Este hook lee una ref durante el renderizado, lo que hoy funciona pero es el tipo de código que la guía de React pide mantener en casos excepcionales.
useOnClickOutsideInvoca un manejador cuando el usuario pulsa en cualquier lugar fuera de un elemento dado. Es típico para cerrar un menú o popover personalizado.
import { useEffect, useRef, type RefObject } from "react";
export function useOnClickOutside(
ref: RefObject<HTMLElement>,
handler: (event: PointerEvent) => void,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
const listener = (event: PointerEvent) => {
if (!ref.current || ref.current.contains(event.target as Node)) return;
saved.current(event);
};
document.addEventListener("pointerdown", listener);
return () => document.removeEventListener("pointerdown", listener);
}, [ref]);
}
Cuándo no usarlo: Si ya usas una biblioteca headless (Radix, Headless UI) para el menú o el diálogo: se encargan por ti de los clics externos, de Escape y de devolver el foco. Un menú hecho a mano solo con este hook suele quedarse sin soporte de teclado.
useIntervalUn setInterval que siempre invoca al callback más reciente y que puede pausarse pasando null como retraso.
import { useEffect, useRef } from "react";
export function useInterval(callback: () => void, delay: number | null) {
const saved = useRef(callback);
useEffect(() => { saved.current = callback; }, [callback]);
useEffect(() => {
if (delay === null) return;
const id = window.setInterval(() => saved.current(), delay);
return () => window.clearInterval(id);
}, [delay]);
}
// useInterval(() => setTicks((t) => t + 1), running ? 1000 : null);
Cuándo no usarlo: Para animaciones (usa CSS o requestAnimationFrame) o para relojes precisos: los intervalos se desfasan y los navegadores los ralentizan en las pestañas en segundo plano. Para una cuenta atrás, calcula el tiempo restante a partir de una marca de tiempo final fija en cada tic.
useWindowSizeDevuelve el ancho y el alto actuales de la ventana y se actualiza al redimensionarla.
import { useSyncExternalStore } from "react";
function subscribe(onChange: () => void) {
window.addEventListener("resize", onChange);
return () => window.removeEventListener("resize", onChange);
}
export function useWindowSize() {
const width = useSyncExternalStore(subscribe, () => window.innerWidth, () => 0);
const height = useSyncExternalStore(subscribe, () => window.innerHeight, () => 0);
return { width, height };
}
Cuándo no usarlo: Para breakpoints: useMediaQuery solo vuelve a renderizar cuando la query cambia de estado, mientras que este vuelve a renderizar con cada píxel del redimensionado. Para el tamaño de un elemento en lugar de la ventana, usa un ResizeObserver. (Las dos instantáneas separadas son intencionadas: devolver un objeto nuevo desde una sola función de instantánea entraría en un bucle infinito.)
useCopyToClipboardCopia texto con la Clipboard API y expone un indicador copied de corta duración para dar respuesta en el botón.
import { useCallback, useState } from "react";
export function useCopyToClipboard(resetAfter = 1500) {
const [copied, setCopied] = useState(false);
const copy = useCallback(async (text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
window.setTimeout(() => setCopied(false), resetAfter);
return true;
} catch {
setCopied(false); // insecure context or permission denied
return false;
}
}, [resetAfter]);
return [copied, copy] as const;
}
Cuándo no usarlo: En páginas servidas por HTTP sin cifrar: navigator.clipboard necesita un contexto seguro (HTTPS o localhost). Muestra siempre una alternativa, como seleccionar el texto, cuando devuelva false.
useFetchCarga JSON desde una URL, sigue los estados de carga y de error, y cancela la petición cuando cambia la URL o el componente se desmonta.
import { useEffect, useState } from "react";
type FetchState<T> = { data: T | null; error: Error | null; loading: boolean };
export function useFetch<T>(url: string | null): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({ data: null, error: null, loading: !!url });
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setState((s) => ({ ...s, loading: true, error: null }));
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
return res.json() as Promise<T>;
})
.then((data) => setState({ data, error: null, loading: false }))
.catch((error: Error) => {
if (error.name === "AbortError") return; // superseded or unmounted
setState({ data: null, error, loading: false });
});
return () => controller.abort();
}, [url]);
return state;
}
Cuándo no usarlo: En apps más grandes. No tiene caché, ni deduplicación, ni reintentos, ni revalidación, así que dos componentes que piden la misma URL hacen dos peticiones. Usa la carga de datos de tu framework o una biblioteca de datos (TanStack Query, SWR) en cuanto necesites algo de eso. La propia documentación de React hace la misma recomendación para cargar datos en efectos.
useEventListenerAñade un listener de eventos a la ventana (o a cualquier elemento) y siempre invoca al manejador más reciente, sin volver a suscribirse en cada renderizado.
import { useEffect, useRef } from "react";
export function useEventListener<K extends keyof WindowEventMap>(
type: K,
handler: (event: WindowEventMap[K]) => void,
target: Window | HTMLElement | null = typeof window === "undefined" ? null : window,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
if (!target) return;
const listener = (event: Event) => saved.current(event as WindowEventMap[K]);
target.addEventListener(type, listener);
return () => target.removeEventListener(type, listener);
}, [type, target]);
}
// useEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
Cuándo no usarlo: Para eventos que React ya gestiona en los elementos que renderizas: usa onClick, onKeyDown y similares directamente en el JSX. Recurre a este hook solo para window, document o elementos de terceros.
useIsMountedDevuelve una función que te dice si el componente sigue en pantalla, para código async heredado que modifica el estado después de un await.
import { useCallback, useEffect, useRef } from "react";
export function useIsMounted() {
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
return useCallback(() => mounted.current, []);
}
// const isMounted = useIsMounted();
// const data = await load(); if (isMounted()) setData(data);
Cuándo no usarlo: Por defecto. React 18 eliminó el aviso sobre modificar el estado de componentes desmontados, y la mejor solución es cancelar el propio trabajo (un AbortController, como en useFetch) o ignorar resultados obsoletos con un indicador dentro de la limpieza del efecto. Resérvalo para código antiguo que todavía no puedes reestructurar.
useState (o useReducer si varios valores cambian a la vez).useRef.useContext, o un store externo leído con useSyncExternalStore.useEffect; cambia a useLayoutEffect solo para mediciones que causan un parpadeo visible.useMemo. Un hijo memorizado sigue renderizándose por una prop de función: useCallback.useDeferredValue o useTransition.useActionState, useFormStatus, useOptimistic.¿Estás creando una biblioteca de componentes en lugar de hooks sueltos? Nuestra comparativa de bibliotecas de UI para React muestra los mismos seis componentes en shadcn/ui, Radix, Headless UI, daisyUI, Flowbite y MUI, y la chuleta de Tailwind cubre las clases utilitarias usadas en las demos de esta página. Tienes piezas de React y Tailwind listas para usar en la galería de componentes.
En el momento de escribir esto (octubre de 2026, React 19.3), la referencia de hooks de React enumera 17 hooks en el propio React: useState, useReducer, useContext, useRef, useImperativeHandle, useEffect, useLayoutEffect, useInsertionEffect, useEffectEvent, useMemo, useCallback, useTransition, useDeferredValue, useDebugValue, useId, useSyncExternalStore y useActionState. React 19 añade además useOptimistic y la API use, y React DOM añade useFormStatus.
useMemo cachea el resultado de llamar a una función, mientras que useCallback cachea la propia función. useCallback(fn, deps) equivale a useMemo(() => fn, deps). Usa useMemo para valores costosos y useCallback para funciones que se pasan a hijos memorizados o se usan como dependencias de efectos.
En desarrollo con Strict Mode activado, React monta cada componente, lo desmonta y lo vuelve a montar para ayudarte a encontrar efectos a los que les falta la limpieza. No ocurre en una build de producción. Si ejecutarlo dos veces provoca un fallo, añade una función de limpieza que deshaga lo que el efecto inició.
Un array vacío le dice a React que el efecto no lee props ni estado, así que se ejecuta una vez después de que el componente se monte y su limpieza se ejecuta cuando el componente se desmonta. Si el efecto sí lee props o estado, inclúyelos; si no, seguirá usando los valores del primer renderizado.
No. use, useActionState, useOptimistic y useFormStatus no los exporta React 18, y useEffectEvent necesita React 19.2 o posterior. En React 18 puedes conseguir un comportamiento parecido con useState y manejadores de eventos para los formularios, y con una ref que guarde el callback más reciente en lugar de useEffectEvent.
Que su nombre empiece por use y que llame al menos a otro hook. Ese nombre permite que las reglas de lint de React comprueben que cumple las reglas de los hooks. Cada componente que invoca un hook personalizado obtiene su propio estado separado; los hooks comparten lógica, no datos.
En vivo: {title}
+3 (updater)
Reiniciar
Leer la sección de useReducer
Añadir tu propio elemento
Nuevo elemento
Nueva tarea pendiente
Borrar completadas
Leo "{theme}" del contexto
Cambiar el valor del provider
Haz clic en el botón para darme el foco
Destino del foco
Enfocar el campo
Añadir a la ref (sin renderizar)
Volver a renderizar
Valor de la ref en el último renderizado: {n}
Escribe aquí
Campo del hijo
El padre invoca shake()
El padre invoca clear()
el efecto se ejecutó: intervalo iniciado
la limpieza se ejecutó: intervalo detenido
Pausar
Reanudar
Cambia mi tamaño escribiendo
Texto a medir
Medido antes del pintado: {n}px de ancho
Color del borde
Esta regla se insertó en el head de la página
Filtra 4,000 elementos
Filtrar elementos
Renderizado no relacionado ({n})
{n} coincidencias. El filtro se ejecutó {runs} veces (los renderizados no relacionados no lo ejecutan).
{label} (el hijo se renderizó {n} veces)
función en línea
Volver a renderizar el padre
Contador: {n}
Acerca de
Lista lenta
Contacto
Cambiando (la pestaña anterior sigue siendo clicable)...
Mostrando: {tab}
Fila
Contenido de la pestaña {tab}.
Resultados renderizados para: "{query}"
Escribe rápido: el campo sigue respondiendo
Buscar
Nombre
Red:
Ancho de la ventana:
Cambia el tamaño de la ventana o desactiva la red para ver cómo se actualiza.
Este componente invoca un hook personalizado que se etiqueta a sí mismo como "{label}" en React DevTools. Abre la pestaña Components para ver la etiqueta junto al hook.
En línea
Sin conexión
arrays de dependencias
[sin array] se ejecutó (renderizado {n})
[] se ejecutó una vez al montar
[a] se ejecutó, a = {n}
Cambiar a ({n})
Cambiar b ({n})
Borrar registro
Escribe y luego para medio segundo
Campo con debounce
Instantáneo: "{text}" · Con debounce: "{debounced}"
Escribe y luego recarga la página
Nombre guardado
Guardado solo en este navegador.
Guardado solo en este navegador: "{name}".
useMediaQuery: el viewport es {size}, el tema del sistema es {theme}.
de 768px o más
de menos de 768px
Activado
Desactivado
Contar {n}
el anterior era {value}
nada todavía
Detener
Iniciar
{n} tics
Copiado
Copiar un comando
Abrir menú
Haz clic en cualquier lugar fuera del menú para cerrarlo.
Empieza gratis · Sin tarjeta de crédito