30% offEnding soon
useActionStateLoading saved progress…

useActionState

useActionState is a React 19 hook that turns an async function into a piece of state: every dispatch folds the previous state into the next one. You give it an async action(previousState, payload) and an initial state; it hands back the current state, a dispatch function, and an isPending flag. Because the action receives the last result as its first argument, successive dispatches accumulate — which is exactly what a form needs, where each submit should see the errors and values from the submit before it. It is, in one line, an async reducer with a built-in pending flag, designed to back <form action={dispatch}>.

Signature

function useActionState<S>(
  action: (previousState: S, payload: unknown) => Promise<S> | S,
  initialState: S,
): [state: S, dispatch: (payload: unknown) => Promise<void>, isPending: boolean];

The action's first argument is the previous state — initialState on the first call, then its own last return value. Whatever it resolves to becomes the new state. dispatch is stable across renders and returns a promise you can await.

Examples

// Folding a number: each dispatch sees the running total.
const [count, dispatch] = useActionState(async (prev, amount) => prev + amount, 0);

dispatch(1); // action(0, 1)  -> state becomes 1
dispatch(5); // action(1, 5)  -> state becomes 6   (folds the LATEST state, not 0)
// A form action returning { values, error }: the next submit sees the last result.
const [result, submit, isPending] = useActionState(
  async (prev, formData) => {
    const email = formData.get('email');
    return { email, error: email ? null : 'Required', tries: prev.tries + 1 };
  },
  { email: '', error: null, tries: 0 },
);
// <form action={submit}> — submit twice and `tries` reads 2, because the second
// run receives the first run's { email, error, tries } as previousState.

Notes

  • The fold is the whole pointdispatch must call action with the latest committed state, so two dispatches in a row chain (the second sees the first's result) instead of both folding off initialState.
  • Pending spans the runisPending is true from the moment you dispatch until the action settles, and returns to false whether it resolves or throws.
  • On a throw — surface the error and clear pending; leave state at its last committed value. React cancels any queued dispatches; pick and document your contract.
  • Stable dispatchdispatch keeps one identity across renders, so passing it to <form action={dispatch}> never re-subscribes.
  • Build it yourself — implement with useState / useRef; do not import React's own useActionState.