Menu

React useActionState Hook: Forms, Errors and Pending

useActionState wraps an async action, keeps the value it last returned, and tells you whether it is still running. Learn the signature, form actions, returning validation errors, and calling it outside a form.

This page includes runnable editors - edit, run, and see output instantly.

useActionState is a React 19 hook that runs an async function (an action), stores whatever it returns as state, and tells you whether it is still running. You call it as const [state, formAction, isPending] = useActionState(action, initialState) and pass formAction to a <form>.

Submit the empty form first: the action returns an error object and the message appears. Then type an address with an @ and submit again. For a second the button reads "Subscribing..." and is disabled.

The syntax

const [state, formAction, isPending] = useActionState(action, initialState, permalink?);
  • action is the function to run. React calls it as action(previousState, ...args). Whatever it returns (or resolves to, if it is async) becomes the new state.
  • initialState is the state before the action has ever run. Use the same shape the action returns, so the component can read state.message on the first render too.
  • state is the latest result.
  • formAction is a wrapped version of your action. Pass it to <form action> or <button formAction>, or call it yourself.
  • isPending is true from the moment the action starts until it finishes.
  • permalink is optional and only matters for Server Functions in a framework: the URL the form should go to if it is submitted before JavaScript loads.

The action receives the previous state as its first argument. This is the most common mistake when moving a form over: writing async function save(formData) gives you the old state in formData, and formData.get throws.

Reading the form with FormData

When formAction is used as a form's action, React calls your action with the form's FormData. Every field with a name is in it. You do not need useState for the inputs or an onChange handler, and you do not call e.preventDefault(): React handles the submit event.

Uncheck Spicy and submit again: the spicy key disappears from the logged data, because an unchecked checkbox sends nothing. The action also reads previousState.count to add one, which is why the previous state comes first.

Returning validation errors

Validation belongs in the action. Return the errors as part of the state, and return the values the user typed so the form can put them back. After a form action finishes, React resets the form's uncontrolled fields, so a field without defaultValue={state.values...} would come back empty after a failed submit.

Type a one-letter name and a short password, then submit. Both errors appear and both fields keep what you typed. Remove defaultValue={state.values.name} and submit again: the name field empties itself after the error.

Keep the server as the final judge in a real app. Client checks give quick feedback, but anyone can send a request without your form.

Calling the action outside a form

formAction is a function, so a button or an effect can call it too. When you call it yourself, wrap the call in startTransition. A form does that for you; a plain onClick does not, and without a transition isPending does not update correctly (React logs an error about it in development).

Click Load more a few times. Each call starts from the state the previous one returned, so pages are appended in order. If you call the action again while it is still running, React queues the call and runs it after the current one, each with the latest state.

Where useActionState came from

Before React 19 was released, this hook was available in canary builds as useFormState, imported from react-dom. It was renamed to useActionState, moved to react, and given the isPending value. If you find old code like this, the fix is the import and the name:

// React canary, before 19
import { useFormState } from 'react-dom';
const [state, formAction] = useFormState(action, initialState);

// React 19
import { useActionState } from 'react';
const [state, formAction, isPending] = useActionState(action, initialState);

To show a pending state in a deeply nested submit button, useFormStatus from react-dom reads the status of the parent form without props. The React 19 overview lists it with the other form features.

Errors and when to use something else

If the action throws, React shows the nearest error boundary instead of updating the state. For errors the user can fix, such as validation or a taken username, return them as state, as in the sign-up example. Let real failures throw.

useActionState is for the result of an action. For a value that updates instantly while the action runs, such as a like count that should go up on click, combine it with useOptimistic. For input that changes on every keystroke and is read during render, plain useState and a controlled input are still the right tool.

Common mistakes

Forgetting the previous state parameter. async function save(formData) receives the previous state in formData. The signature is always (previousState, formData) for a form.

Inputs without a name. FormData only contains fields that have a name attribute. A field with only an id is missing from formData.get(...), which returns null.

Controlled inputs everywhere. You can still use value and onChange inside a form action, but you do not have to. Uncontrolled fields with name and defaultValue are the simpler match for actions, and React's reset after submit only affects uncontrolled fields.

Reading state inside the action. The state from the component is the value from the render that created the handler. Inside the action, use previousState, which is always the result of the call before it, even when several calls were queued.

Throwing for expected errors. A thrown error replaces the form with the nearest error boundary. A wrong password is not a crash; return it as state.

Frequently Asked Questions

What does useActionState return?

An array of three items: the current state (the value the action last returned, or the initial state before it has run), a new action to pass to a form or call yourself, and isPending, which is true while the action runs.

What arguments does the action receive?

The previous state first, then whatever the action was called with. In a <form action={formAction}> that second argument is the form's FormData.

What is the difference between useActionState and useFormState?

They are the same hook. It shipped in React canaries as useFormState in react-dom, was renamed to useActionState and moved to react for React 19, and gained the isPending value at the same time.

Can I use useActionState without a form?

Yes. Call the returned action yourself, inside startTransition, for example from a button's onClick. Without a transition, isPending does not update correctly and React logs an error in development.

Why does my form clear after submitting?

After a form action finishes, React resets the form's uncontrolled fields, as a browser does after a normal submit. To keep a value after an error, return it in the state and pass it to the field's defaultValue.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED