Next.js 33: Pending & Form State with useActionState / useFormStatus
easy⏱ 5 mincoursenextjs
useActionState: state + pending in one hook
useActionState takes your action and an initial state, and gives back a tuple: the latest state your action returned, a formAction you wire into the form, and an isPending boolean that's true for the whole time the action is running — across every await inside it. The action itself receives (previousState, formData), matching the tuple's state.
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 without prop drilling
useFormStatus() is imported from react-dom, not react. Called inside any descendant of a <form>, it returns { pending, data, method, action } — the status of the nearest parent form's ongoing submission. A reusable SubmitButton can read its own parent form's pending state directly, with zero props passed down from the page.
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Posting…' : 'Post'}
</button>
);
}
Simulating in React 18
This sandbox runs React 18, which has neither hook. We simulate useActionState with a tiny wrapper around useState + useTransition (so isPending stays true across the whole await), and simulate useFormStatus by passing pending down as a normal prop instead — same visible behavior, different plumbing.
Real pending UI
Wrap a useState + useTransition pair into a small useSimulatedActionState(action, initialState) helper returning [state, run, isPending]. Use it to drive your comment form: disable the input and the SubmitButton while isPending, and show 'Posting…' on the button. Keep export default App.