useActionState is React 19's answer to the mess of useState + useEffect + isLoading flags that every form component used to require. It takes an async action function and an initial state, and gives you back the current state, a form action, and a pending boolean. One hook, entire form lifecycle handled.
It replaces the short-lived useFormState from the React canary channel and works with both server actions (Next.js, Remix) and plain client-side async functions. The big deal: forms built with useActionState work before JavaScript loads, because the action prop triggers native form submission as a fallback.
Master react useactionstate hook
Take the React 19 & Patterns course with hands-on lessons and challenges.
A real login form with Zod validation on the server. The action returns field-level errors that map directly to the UI. If validation passes but credentials are wrong, a form-level error is returned instead. On success, it creates a session and redirects. The form works without JavaScript because loginAction is a server action.
useActionState works perfectly with plain async functions that run on the client. No 'use server' needed. This is useful when you don't have a server component framework or when the logic is purely client-side. The action calls a REST endpoint and maps HTTP status codes to user-facing messages.
The previousState parameter shines when you need to track information across submissions. Here, attemptCount increments on every submission regardless of success. You could use this to show a 'still having trouble?' help link after three failed attempts, rate-limit submissions, or preserve form values that were valid while only re-showing the invalid fields.
File inputs work naturally with useActionState because FormData handles files natively. The server action receives the File object, validates it, uploads it to storage, and returns the new URL. The initial state uses the current avatar URL so the preview shows immediately. After upload, revalidatePath ensures the layout re-renders with the new image.
This is the full React 19 forms stack. useActionState manages the error state and wraps the server action. Inside the wrapper, useOptimistic inserts a temporary comment at the top of the list before the server responds. useFormStatus powers the submit button in a child component so it disables during submission. If the server action fails, the optimistic comment disappears automatically and the error message shows.
Side-by-side comparison that shows the real improvement. The old pattern needed six useState calls, a manual try/catch/finally, and e.preventDefault(). The new version is one hook, no controlled inputs, no manual loading state management. The form also gains progressive enhancement — it can submit without JavaScript loaded.
Swapping the parameter order in the action function — writing `(formData, previousState)` instead of `(previousState, formData)`
The action function signature is always `(previousState, formData)`. Previous state comes first, like a reducer. If you swap them, you'll call `.get()` on the state object and get a confusing runtime error. Think of it as `(currentState, input) => newState`, matching the reducer pattern.
Forgetting to return a value from every code path in the action function, causing state to become `undefined`
Every branch of your action must return the full state shape. If your type is `{ error: string | null; success: boolean }`, return that exact shape from the validation failure branch, the API error branch, and the success branch. TypeScript helps here — type the action function's return value explicitly.
Trying to use `useFormStatus` in the same component that renders the `<form>` tag
useFormStatus reads status from the nearest parent `<form>`. It must be called in a child component rendered inside the form, not in the component that renders the form element itself. Extract your submit button into a `SubmitButton` component and call useFormStatus there.
Using `onSubmit` with `e.preventDefault()` on a form that also has an `action` prop, which silently blocks the action from firing
Pick one pattern. If you're using the `action` prop with useActionState, remove the `onSubmit` handler entirely. If you need to run client-side logic before submission (like analytics), use it without `preventDefault()` — but be aware that mixing both patterns is fragile and hard to debug.
useActionState is the React 19 hook that replaces the boilerplate of managing form state, loading flags, and error handling with a single call. It returns `[state, formAction, isPending]`, where the action function follows the `(previousState, formData) => newState` signature. It works with server actions for progressive enhancement and with client-side async functions for SPAs. Combine it with useFormStatus for reusable pending UI and useOptimistic for instant feedback. The result is less code, fewer bugs, and forms that work before JavaScript loads.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.