Chuleta de React hooks: cada hook con un ejemplo en vivo

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.

Todos los hooks integrados de un vistazo

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.

Mostrando los 20
HookGrupoÚsalo paraDesde
useStateEstadoguardar un valor que vuelve a renderizar el componente cuando cambiaReact 18
useReducerEstadogestionar estado con muchas actualizaciones relacionadas mediante una función reductoraReact 18
useContextContextoleer un valor del provider más cercano por encimaReact 18
useRefRefguardar un valor mutable o un nodo del DOM que no provoca renderizadosReact 18
useImperativeHandleRefelegir qué métodos puede llamar un padre a través de una refReact 18
useEffectEfectosincronizar con algo externo a React después del pintadoReact 18
useLayoutEffectEfectomedir el layout antes de que el navegador pinteReact 18
useInsertionEffectEfectoinsertar reglas de estilo antes de que se ejecuten los efectos de layout (bibliotecas CSS-in-JS)React 18
useEffectEventEfectoleer las props más recientes dentro de un efecto sin volver a ejecutarloReact 19.2
useMemoRendimientocachear un cálculo costoso entre renderizadosReact 18
useCallbackRendimientocachear una función para que los hijos memorizados no se vuelvan a renderizarReact 18
useTransitionRendimientomarcar una actualización de estado como no urgente y obtener un indicador de pendienteReact 18
useDeferredValueRendimientodejar que una parte lenta de la UI vaya por detrás de la escritura rápidaReact 18
useIdOtrosgenerar ID únicos y estables para etiquetas y atributos ARIAReact 18
useSyncExternalStoreOtrossuscribirse a un store que vive fuera de ReactReact 18
useDebugValueOtrosetiquetar un hook personalizado en React DevToolsReact 18
useActionStateOtrosseguir el resultado y el estado pendiente de una acción de formularioReact 19
useOptimisticOtrosmostrar el resultado esperado de una acción antes de que termineReact 19
useAPIleer una promesa o un contexto, incluso dentro de condicionesReact 19
useFormStatusReact DOMleer el estado pendiente del formulario padreReact 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.

Hooks de estado

useState

Qué 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" }).

useReducer

Qué 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.

Hooks de contexto

useContext

Qué 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.

Hooks de ref

useRef

Qué 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.

useImperativeHandle

Qué 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.

Hooks de efecto

useEffect

Qué 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.

useLayoutEffect

Qué 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.

useInsertionEffect

Qué 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.

useEffectEvent

Qué 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.

Hooks de rendimiento

useMemo

Qué 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.

useCallback

Qué 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.

useTransition

Qué 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.

useDeferredValue

Qué 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).

Otros hooks

useId

Qué 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.

useSyncExternalStore

Qué 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.

useDebugValue

Qué 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()).

Hooks de React 19 (solo código, no ejecutables en React 18)

Solo React 19. Los cuatro hooks de esta sección (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.

useActionState

Qué 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.

useOptimistic

Qué 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.

use

Qué 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.

useFormStatus

Qué 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.

El array de dependencias, explicado

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:

EscribesEl efecto se ejecutaUso típico
useEffect(fn)después de cada renderizadopoco 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 desmontarsuscripciones 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 bcargar 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.

Cinco reglas que evitan la mayoría de fallos de dependencias

  1. Incluye todos los valores reactivos que lee el callback. Props, estado y todo lo declarado en el cuerpo del componente. Dejar uno fuera te da un closure obsoleto: el callback conserva el valor de un renderizado antiguo. Instala eslint-plugin-react-hooks y deja que exhaustive-deps te diga qué falta.
  2. Los objetos y funciones creados durante el renderizado son nuevos cada vez. { 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.
  3. Depende de primitivos siempre que puedas. [user.id] es más estable que [user] cuando solo importa el ID.
  4. Usa funciones de actualización para eliminar dependencias de estado. setCount(c => c + 1) no lee count, así que no hace falta incluir count.
  5. No silencies el linter para "arreglar" un bucle. Un bucle infinito suele significar que el efecto modifica un estado que también es dependencia, o que depende de un objeto nuevo. Arregla la causa. Para valores que quieres leer sin reaccionar a ellos, usa 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]);

12 hooks personalizados para copiar en tu proyecto

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.

1. useDebounce

Devuelve 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.

2. useLocalStorage

Funciona 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.

3. useMediaQuery

Devuelve 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.

4. useToggle

Un 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.

5. usePrevious

Devuelve 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.

6. useOnClickOutside

Invoca 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.

7. useInterval

Un 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.

8. useWindowSize

Devuelve 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.)

9. useCopyToClipboard

Copia 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.

10. useFetch

Carga 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.

11. useEventListener

Añ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.

12. useIsMounted

Devuelve 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.

¿Qué hook necesito? Una lista rápida para decidir

¿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.

Preguntas frecuentes

¿Cuántos hooks integrados tiene React?

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.

¿Cuál es la diferencia entre useMemo y useCallback?

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.

¿Por qué mi useEffect se ejecuta dos veces?

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ó.

¿Qué significa un array de dependencias vacío en useEffect?

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.

¿Puedo usar hooks de React 19 como use o useActionState en React 18?

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.

¿Qué convierte una función en un hook personalizado?

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

Email

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

Relacionado: Guías

Producto

Para quién es

Funciones

Recursos

Empresa

Mapa del sitio