Hooks de React 19: use, useActionState y useFormStatus

Avanzado
React
React
Actualizado: 26/04/2026

Por qué React 19 añade nuevos hooks

Antes de React 19, gestionar un formulario con envío asíncrono requería mezclar useState, useEffect y banderas manuales como loading, error o result. Lo mismo ocurría con la lectura de datos remotos, donde el patrón habitual era useEffect(() => { fetch(...).then(...) }, []) con tres estados distintos.

React 19 introduce tres hooks dedicados que reducen ese boilerplate y se integran con Suspense y Server Components:

| Hook | Resuelve | |-------------------|---------------------------------------------------------------------| | use() | Leer promesas y context dentro del render, sin useEffect | | useActionState | Gestionar el estado de una acción asíncrona vinculada a un <form> | | useFormStatus | Saber si el formulario padre está enviando datos sin prop drilling |

Los tres se complementan: use() simplifica fetching, useActionState simplifica envío de formularios y useFormStatus simplifica la UI durante ese envío.

flowchart LR
    A[Componente padre] -->|datos por acción| B(useActionState)
    B -->|state actualizado| C[UI]
    A -->|wrap form| D[form action]
    D -->|status compartido| E(useFormStatus)
    E --> F["Boton hijo / spinner / mensaje"]
    G[Promesa o Context] --> H(use)
    H --> C

use(): leer promesas y context inline

use() es el primer hook de React que acepta promesas y se suspende mientras se resuelven. Internamente integra con <Suspense>, así que no hace falta useState + useEffect para fetching simple.

Leer una promesa con Suspense

import { use, Suspense } from 'react';

function fetchUser(userId) {
  return fetch(`/api/users/${userId}`).then(r => r.json());
}

function UserProfile({ userPromise }) {
  const user = use(userPromise);
  return <h2>{user.name}</h2>;
}

export default function App() {
  const userPromise = fetchUser(42);
  return (
    <Suspense fallback={<p>Cargando perfil...</p>}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

Importante: la promesa no debe crearse dentro del componente que llama a use(), porque cada render generaría una promesa nueva y entraría en bucle infinito. La promesa se crea en el padre o en una caché (por ejemplo TanStack Query, SWR, Next.js o cache() en RSC).

Leer context condicionalmente

A diferencia de useContext, use() puede llamarse dentro de condicionales y bucles. Esto resulta útil para componentes que solo necesitan un context cuando se cumple una condición:

import { use, createContext } from 'react';

const ThemeContext = createContext(null);

function Banner({ premium }) {
  if (!premium) return null;
  const theme = use(ThemeContext);
  return <div className={`banner ${theme.color}`}>Premium</div>;
}

useContext lanzaría un warning por estar dentro de if; use() está diseñado precisamente para soportar esos escenarios.

useActionState: formularios con envío asíncrono

useActionState retorna un triplete [state, formAction, isPending] donde:

  • state es el resultado de la última ejecución de la acción.
  • formAction es una función que se pasa directamente al atributo action del <form>.
  • isPending indica si el envío sigue en curso.

Su firma recibe la acción (que puede ser async) y el estado inicial:

import { useActionState } from 'react';

async function suscribirAccion(prevState, formData) {
  const email = formData.get('email');
  if (!email?.includes('@')) {
    return { ok: false, message: 'Email invalido' };
  }
  const res = await fetch('/api/newsletter', {
    method: 'POST',
    body: JSON.stringify({ email }),
    headers: { 'Content-Type': 'application/json' },
  });
  if (!res.ok) {
    return { ok: false, message: 'Error en el servidor' };
  }
  return { ok: true, message: 'Suscrito correctamente' };
}

export default function FormularioNewsletter() {
  const [state, formAction, isPending] = useActionState(suscribirAccion, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Enviando...' : 'Suscribirme'}
      </button>
      {state && (
        <p style={{ color: state.ok ? 'green' : 'red' }}>{state.message}</p>
      )}
    </form>
  );
}

Comparado con la versión equivalente con useState + onSubmit + useState pending + useState message, el código se reduce a la mitad y queda libre de race conditions porque React serializa las llamadas a la acción.

useFormStatus: estado del formulario desde un hijo

useFormStatus lee el estado del formulario padre más cercano sin necesidad de pasar props. Solo funciona en componentes renderizados dentro de un <form> que usa action.

import { useFormStatus } from 'react-dom';

function BotonEnvio() {
  const { pending, data, method } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Procesando...' : 'Guardar cambios'}
    </button>
  );
}

function MensajeProgreso() {
  const { pending } = useFormStatus();
  if (!pending) return null;
  return <span role="status" aria-live="polite">Enviando...</span>;
}

export default function FormularioPerfil({ guardar }) {
  return (
    <form action={guardar}>
      <input name="nombre" />
      <BotonEnvio />
      <MensajeProgreso />
    </form>
  );
}

Cualquier hijo del formulario puede consultar el estado sin que el padre tenga que distribuir un prop pending. Esto encaja muy bien con sistemas de diseño donde el <Button> desactiva su click automáticamente si está dentro de un formulario en envío.

useFormStatus se importa desde react-dom, no desde react, porque se aplica solo a entornos con <form> real.

Combinar los tres hooks

El patrón canónico en una página con formulario y datos remotos es:

  • El padre crea la promesa con datos iniciales y los pasa al hijo, que los lee con use().
  • Un useActionState gobierna el envío.
  • Un <SubmitButton /> interno usa useFormStatus para reflejar el estado.
import { use, useActionState, Suspense } from 'react';
import { useFormStatus } from 'react-dom';

async function cargarPerfil(id) {
  const r = await fetch(`/api/usuarios/${id}`);
  return r.json();
}

async function guardarPerfil(prev, formData) {
  const res = await fetch('/api/usuarios', {
    method: 'PUT',
    body: formData,
  });
  return res.ok ? { ok: true } : { ok: false, msg: 'Error' };
}

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? 'Guardando...' : 'Guardar'}</button>;
}

function FormularioPerfil({ perfilPromise }) {
  const perfil = use(perfilPromise);
  const [state, action] = useActionState(guardarPerfil, null);
  return (
    <form action={action}>
      <input name="id" type="hidden" defaultValue={perfil.id} />
      <input name="nombre" defaultValue={perfil.nombre} />
      <SubmitButton />
      {state?.ok === false && <p>{state.msg}</p>}
    </form>
  );
}

export default function PaginaPerfil({ id }) {
  const perfilPromise = cargarPerfil(id);
  return (
    <Suspense fallback={<p>Cargando perfil...</p>}>
      <FormularioPerfil perfilPromise={perfilPromise} />
    </Suspense>
  );
}

Cuándo seguir usando useState + useEffect

Estos hooks no sustituyen al patrón clásico en todos los escenarios:

  • Estado puramente local (contadores, toggles, búsquedas en memoria): useState sigue siendo la opción correcta.
  • Polling, suscripciones a eventos o WebSockets: useEffect es necesario porque hay limpieza explícita.
  • Fetching imperativo en respuesta a un click (no un envío de formulario): mejor useState para el resultado y un handler asíncrono.

Los nuevos hooks brillan en formularios, lectura de datos al montar y composición con server actions. No son un reemplazo total del modelo previo.

Compatibilidad y ESLint

useActionState y useFormStatus requieren React 19.0+ (cliente y servidor). En servidores RSC se mapean a server actions y funcionan tanto en formularios HTML reales como con JavaScript desactivado.

El plugin eslint-plugin-react-hooks v5.0+ ya incluye reglas para los nuevos hooks: detecta promesas creadas dentro del componente que las consume con use(), y avisa cuando useFormStatus se usa fuera de un formulario. Si tu proyecto sigue en React 18, instala react@19 y actualiza el plugin antes de adoptar estos patrones.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, React es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de React

Explora más contenido relacionado con React y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Usar use() para leer promesas y context de forma síncrona dentro del render. Implementar useActionState para reducir boilerplate en formularios con acciones asíncronas. Aplicar useFormStatus desde botones y mensajes hijos para reflejar el estado del formulario sin prop drilling. Combinar Suspense con use() para fetching declarativo. Diferenciar cuándo conviene cada hook frente a useState + useEffect clásico.