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 ocache()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:
statees el resultado de la última ejecución de la acción.formActiones una función que se pasa directamente al atributoactiondel<form>.isPendingindica 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.
useFormStatusse importa desdereact-dom, no desdereact, 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
useActionStategobierna el envío. - Un
<SubmitButton />interno usauseFormStatuspara 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):
useStatesigue siendo la opción correcta. - Polling, suscripciones a eventos o WebSockets:
useEffectes necesario porque hay limpieza explícita. - Fetching imperativo en respuesta a un click (no un envío de formulario): mejor
useStatepara 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
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.