DevAcademy
LearnReactuseActionState
AdvancedReact

useActionState

Learn how useActionState tracks a form Action’s pending state and result without manually wiring up separate state variables.

Reading Time

16 min

Lesson

Lesson 29 of 42

What is an Action?

An Action is an async function passed to a form’s action prop, or triggered via startTransition. React automatically tracks its pending state and handles the surrounding form submission, without you needing to call event.preventDefault() or manage a loading flag by hand.

A Plain Action (No Extra State Tracking)

function UpdateNameForm() {
  async function updateName(formData) {
    const name = formData.get("name");
    await saveName(name);
  }

  return (
    <form action={updateName}>
      <input type="text" name="name" />
      <button type="submit">Save</button>
    </form>
  );
}

What useActionState Adds

useActionState wraps an Action and gives back its current result (like a validation error or success message) and a pending flag — both updated automatically as the Action runs, without separate useState calls for each.

useActionState with Validation

import { useActionState } from "react";

function ChangeNameForm() {
  const [error, submitAction, isPending] = useActionState(
    async (previousError, formData) => {
      const name = formData.get("name");
      if (!name) {
        return "Name is required";
      }
      await saveName(name);
      return null; // no error
    },
    null // initial state
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button disabled={isPending}>{isPending ? "Saving..." : "Save"}</button>
      {error && <p className="error">{error}</p>}
    </form>
  );
}

useActionState Arguments and Return Value

ItemPurpose
fn(previousState, formData)The action function; receives the previous state and the submitted form data
initialStateThe state value used before the action has run for the first time
stateThe current state — whatever the action function last returned
formActionPass this to the form’s action prop (or a button’s formAction prop)
isPendingtrue while the action is in flight

Comparing to Manual State Management

Before useActionState, the same behavior required separate useState calls for the error/result and the pending flag, plus manually setting and resetting both around the async call — useActionState consolidates all of that into one hook tied directly to the action.

The Manual Equivalent (More Boilerplate)

function ChangeNameForm() {
  const [error, setError] = useState(null);
  const [isPending, setIsPending] = useState(false);

  async function handleSubmit(event) {
    event.preventDefault();
    setIsPending(true);
    const name = new FormData(event.target).get("name");
    if (!name) {
      setError("Name is required");
      setIsPending(false);
      return;
    }
    await saveName(name);
    setError(null);
    setIsPending(false);
  }

  return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}

Best Practice

Reach for useActionState for form submissions that need to track a pending state and a result (success message or validation error) — it removes a very common cluster of boilerplate state management.

Interview Questions

Quick Quiz

1. What does useActionState track automatically?

2. What arguments does the function passed to useActionState receive?

3. What is the main benefit of useActionState over manually wiring useState for pending/error?