Trust is earned, not given

A different perspective

2025-02-04 · Projects

React.js, part 12: React 19 Actions and useActionState — the form lifecycle, finally built in

Part 12from the React.js series · 20 parts in all

By 2025 the biggest React releases were no longer about new rendering features but about finishing half-built abstractions. React 19 completed the form story: actions became a first-class concept, and the "submit, validate, show errors, track pending" loop collapsed into one hook.

An action is any async function passed to a form

You do not need a server for this — an action can be a client function. React calls it, awaits it, and manages the pending state of the form around it:

async function saveQuote(formData) {
  const res = await fetch('/api/quotes', { method: 'POST', body: formData });
  if (!res.ok) return { error: 'Could not save the quote.' };
  return { savedAt: Date.now() };   // becomes `state` below
}

useActionState: state + action + pending, in one call

'use client';

import { useActionState } from 'react';

export function QuoteForm() {
  // state    - the last return value of the action (undefined until it runs)
  // action   - pass to <form action={...}>, React handles submit + pending
  // pending  - true while the action is in flight
  const [state, action, pending] = useActionState(saveQuote, null);

  return (
    <form action={action}>
      <input name="customer" required />
      <textarea name="body" required />
      <button disabled={pending}>{pending ? 'Saving…' : 'Save quote'}</button>
      {state?.error && <p role="alert">{state.error}</p>}
      {state?.savedAt && <p>Saved.</p>}
    </form>
  );
}

Three things disappeared: the useState for the result, the isSubmitting boolean, and the onSubmit handler with preventDefault. All three are now React's job.

The rest of the family

The mental shift

React now owns the transition from "form interacted with" to "action finished". Your components describe what the UI should look like in the resulting state, exactly as they do for every other piece of React state. That consistency is the real feature — the form is no longer a special case with its own imperative encoding. Next: pending states done properly.