Actions & useActionState

React 19 Actions handle form submission, pending state and results in one hook.

Syntaxconst [state, formAction, pending] = useActionState(action, initial);

React 19 introduces Actions: functions you pass to a form's action prop to handle submission, including async work. The useActionState hook wires an action to state and a pending flag.

What useActionState gives you

  • The latest state returned by your action (e.g. a result or error message).
  • A wrapped action to pass to <form action={...}>.
  • A pending boolean that is true while the action runs.

Your action receives the previous state and the submitted FormData, and returns the next state.

Example

Example Β· javascript
import { useActionState } from 'react';

async function submit(prev, formData) {
  const name = formData.get('name');
  await new Promise((r) => setTimeout(r, 500));
  return { message: 'Hello, ' + name };
}

function NameForm() {
  const [state, formAction, pending] = useActionState(submit, { message: '' });
  return (
    <form action={formAction}>
      <input name="name" />
      <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>
      <p>{state.message}</p>
    </form>
  );
}

When to use it

  • A comment form uses useActionState so the pending flag disables the submit button automatically while the server processes the POST, preventing double-submissions.
  • A login form wires its server action through useActionState and displays the returned error message (e.g., 'Invalid credentials') without managing a separate error state variable.
  • A file-upload form uses an async action with useActionState to show an in-progress indicator and then the server response β€” all without writing fetch boilerplate.

More examples

Basic useActionState form

Wires an async server action to a form, automatically tracking pending state and the returned result.

Example Β· jsx
import { useActionState } from 'react';

async function submitAction(prevState, formData) {
  const name = formData.get('name');
  await new Promise(r => setTimeout(r, 800)); // simulate server
  return `Hello, ${name}!`;
}

function GreetForm() {
  const [result, action, isPending] = useActionState(submitAction, null);
  return (
    <form action={action}>
      <input name="name" placeholder="Your name" />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Sending...' : 'Submit'}
      </button>
      {result && <p>{result}</p>}
    </form>
  );
}

Action returning an error object

Shows an action returning a structured error object that the component displays without a separate error state.

Example Β· jsx
import { useActionState } from 'react';

async function loginAction(prev, formData) {
  const email = formData.get('email');
  const res = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify({ email }),
    headers: { 'Content-Type': 'application/json' },
  });
  if (!res.ok) return { error: 'Invalid credentials.' };
  return { error: null };
}

function LoginForm() {
  const [state, action, pending] = useActionState(loginAction, { error: null });
  return (
    <form action={action}>
      <input name="email" type="email" />
      {state.error && <p className="error">{state.error}</p>}
      <button disabled={pending}>Log in</button>
    </form>
  );
}

Progressive enhancement with action

Replaces the form with a success message when the action returns a subscribed flag, requiring no extra useState.

Example Β· jsx
import { useActionState } from 'react';

async function subscribeAction(prev, formData) {
  const email = formData.get('email');
  await fetch('/api/subscribe', { method: 'POST', body: new URLSearchParams({ email }) });
  return { subscribed: true, email };
}

function NewsletterForm() {
  const [state, action, pending] = useActionState(subscribeAction, null);
  if (state?.subscribed) return <p>Subscribed as {state.email}!</p>;
  return (
    <form action={action}>
      <input name="email" type="email" placeholder="[email protected]" />
      <button disabled={pending}>{pending ? 'Subscribing...' : 'Subscribe'}</button>
    </form>
  );
}

Discussion

  • Be the first to comment on this lesson.