React Best Practices

Chapter 5 · Practice 2 of 5

Track results and errors with useActionState

From Forms and actions in React 19

Why it matters

useActionState(action, initialState) returns three things:

  • the current state,
  • a wrapped action to pass to the form,
  • an isPending flag.

Your action receives the previous state and the form data, and whatever it returns becomes the new state. That makes it the clean place to return validation errors or a success message.

Example

TSX
async function signUp(prev: State, formData: FormData): Promise<State> {
  const email = String(formData.get('email'));
  if (!email.includes('@')) return { error: 'Enter a valid email' };
  await createAccount(email);
  return { error: null };
}

function SignUpForm() {
  const [state, formAction, isPending] = useActionState(signUp, { error: null });
  return (
    <form action={formAction}>
      <input name="email" />
      <button disabled={isPending}>Sign up</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}