Next.js 33: Estado pendiente y de formulario con useActionState / useFormStatus
easy⏱ 5 mincoursenextjs
useActionState: state + pending en un solo hook
useActionState toma tu acción y un estado inicial, y devuelve una tupla: el último state que devolvió tu acción, una formAction que conectas al formulario, y un booleano isPending que es true durante todo el tiempo que la acción está corriendo — a través de cada await dentro de ella. La acción misma recibe (previousState, formData), en sintonía con el state de la tupla.
import { useActionState } from 'react';
async function commentAction(prevState, formData) {
const text = formData.get('comment');
await saveComment(text);
return { success: true };
}
function CommentForm() {
const [state, formAction, isPending] = useActionState(commentAction, null);
return (
<form action={formAction}>
<input name="comment" disabled={isPending} />
<button disabled={isPending}>{isPending ? 'Posting…' : 'Post'}</button>
</form>
);
}
useFormStatus: pending sin prop drilling
useFormStatus() se importa desde react-dom, no de react. Llamado dentro de cualquier descendiente de un <form>, devuelve { pending, data, method, action } — el estado del envío en curso del formulario padre más cercano. Un SubmitButton reutilizable puede leer directamente el estado pendiente de su propio formulario padre, sin que la página le pase ninguna prop.
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Posting…' : 'Post'}
</button>
);
}
Simulando en React 18
Este sandbox corre React 18, que no tiene ninguno de los dos hooks. Simulamos useActionState con un pequeño envoltorio alrededor de useState + useTransition (para que isPending siga en true durante todo el await), y simulamos useFormStatus pasando pending como una prop normal — el mismo comportamiento visible, distinta fontanería.
UI de pendiente real
Envuelve un par useState + useTransition en un pequeño helper useSimulatedActionState(action, initialState) que devuelva [state, run, isPending]. Úsalo para manejar tu formulario de comentarios: deshabilita el input y el SubmitButton mientras isPending, y muestra 'Posting…' en el botón. Mantén export default App.