React 19 de Cero a Experto / React 19 moderno
Actions y useActionState: formularios que se manejan solos
Enviar un formulario a un servidor siempre llevó el mismo ritual manual: un estado enviando, un
estado error, un try/catch, poner y quitar el disabled… en cada formulario, una y otra vez.
React 19 lo automatiza con las ACTIONS y el hook useActionState: le das la función que hace el
trabajo y React te maneja el estado pendiente y el resultado. Hoy ves el "antes" doloroso —que
corre en vivo— y el "después" de React 19. Es uno de los cambios más prácticos del framework.
El mesero que anota y te avisa
El "antes": todo a mano (corre en vivo)
Así se hacía —y se sigue haciendo en React 18—: cada pedazo de estado, manual. Este playground funciona; fíjate en cuánta contabilidad hay para algo tan común:
Cuenta los estados: enviando, error, ok, más el try/catch/finally y el no-olvidar-apagar
el disabled. Es correcto, pero es el MISMO boilerplate en cada formulario de la app. Aquí es donde
React 19 dice "yo me encargo".
El "después": useActionState
Una Action es simplemente una función asíncrona que recibe los datos del formulario y hace el
trabajo. useActionState la envuelve y te devuelve el estado del resultado, la versión "envolvida"
de la acción para el <form>, y —gratis— un booleano pendiente:
import { useActionState } from "react";
type Estado = { error?: string; ok?: boolean };
// Una ACTION: recibe el estado previo y el FormData del formulario.
async function guardarGasto(_previo: Estado, formData: FormData): Promise<Estado> {
const nombre = String(formData.get("nombre") ?? "");
if (!nombre.trim()) return { error: "El nombre es obligatorio" };
await guardarEnServidor(nombre);
return { ok: true };
}
export default function App() {
// [estado del resultado, la acción para el form, ¿está pendiente?]
const [estado, accionForm, pendiente] = useActionState(guardarGasto, {});
return (
<form action={accionForm} style={{ padding: 24 }}>
<input name="nombre" placeholder="Nombre del gasto" />
{/* 'pendiente' lo maneja React: true mientras la action corre */}
<button disabled={pendiente}>{pendiente ? "Guardando…" : "Guardar"}</button>
{estado.error && <p style={{ color: "#ef4444" }}>⚠ {estado.error}</p>}
{estado.ok && <p style={{ color: "#22c55e" }}>✓ Guardado</p>}
</form>
);
}Compara con el "antes": desaparecen los useState de enviando, error y ok, el
preventDefault, el try/finally para apagar el disabled. useActionState(fn, estadoInicial)
te da tres cosas: el estado que devuelve tu acción, una accionForm que pasas a <form action={...}>,
y pendiente que React pone en true mientras la acción corre. Tú solo escribes la lógica del
envío y devuelves el nuevo estado; el seguimiento lo lleva React.
Cómo encajan las piezas
Un apunte de honestidad
¿Qué te devuelve useActionState(accion, estadoInicial) y qué maneja React por ti?
Mini-reto
En tu proyecto React 19 (o en papel, razonando el flujo): 1) escribe una acción suscribir(previo, formData)
que lea un email del FormData, valide que contenga "@" y devuelva { error } o { ok: true };
2) conéctala con useActionState y un <form action={...}> con un input name="email"; 3) usa el
tercer valor pendiente para deshabilitar el botón y mostrar "Enviando…". Compara cuántas líneas te
ahorras frente al "antes" manual del playground.
Qué sigue
Ya delegas el estado pendiente. Pero hay una experiencia aún más pulida: que la UI responda AL
INSTANTE, antes de que el servidor confirme —y se corrija sola si algo falla—. Eso es la UI
optimista, y React 19 trae un hook dedicado: useOptimistic. La próxima lección lo desarma.