06 May 2024
Hector Sosa

Simplify Form Handling in React 19: Introducing `useActionState` Hook


Do you need a reliable partner in tech for your next project?

React 19's new useActionState hook simplifies handling asynchronous operations in React, especially when dealing with forms. Let's delve into a practical example to see it in action.
Here we have a simple read-only form ready for submission:
1"use client";
3export const FormAction = () => {
4  return (
5    <div>
6      <form>
7        <input type="text" name="name" value="Hello from actions" readOnly />
8        <button type="submit">Submit</button>
9      </form>
10      <footer>
11        <p>Awaiting action 🚀</p>
12      </footer>
13    </div>
14  );
The useActionState hook takes a minimum of two arguments: (1) an asynchronous action, that in turn takes its own arguments of previousState and a generic payload, and (2) an initial state. This hook returns (a) the awaited state, (b) a dispatcher and (c) an isPending boolean.
1// canary.d.ts
2export function useActionState<State, Payload>(
3  action: (state: Awaited<State>, payload: Payload) => State | Promise<State>,
4  initialState: Awaited<State>
5): [
6  state: Awaited<State>,
7  dispatch: (payload: Payload) => void,
8  isPending: boolean
So let's define our state's type definition along with our initial state value to begin shaping our component.
1"use client";
3import { useActionState } from "react";
5type State = { data: string; status: "SUCCESS" } | { status: "ERROR" | "INIT" };
6const initState: State = { status: "INIT" };
8export const FormAction = () => {
9  const [action, submitAction, isPending] = useActionState(
10    async (prevState: State, formData: FormData) =>
11      runAction(prevState, String(formData.get("name"))),
12    initState
13  );
15  return (
16    <div>
17      <form action={submitAction}>
18        <input type="text" name="name" value="Hello from actions" readOnly />
19        <button type="submit" disabled={isPending}>
20          Submit
21        </button>
22      </form>
23      <footer>
24        {action.status === "INIT" && <p>Awaiting action 🚀</p>}
25        {action.status === "SUCCESS" && <p>Success, all good ✅</p>}
26        {action.status === "ERROR" && <p>Error, please resubmit action ❌</p>}
27        <code>{JSON.stringify({ isPending })}</code>
28        <code>{JSON.stringify(action)}</code>
29      </footer>
30    </div>
31  );
  • Notice how submitAction, a function generated by useActionState, is used directly in the form's action attribute. This moves away from the form pattern of using callbacks onSubmit.
  • The submission button is disabled based on isPending which allows us to manage state effectively.
  • As for the form's feedback mechanism, it responds dynamically to changes in action's state.
The runAction function here is a mock, simulating an API call which randomly succeds or fails returning a new state, updating the form's status to either SUCCESS or ERROR. This could or could not be a Server Action.
1async function runAction(_prevState: State, data: string) {
2  return new Promise<State>((r) => {
3    setTimeout(
4      () =>
5        Math.random() < 0.5
6          ? r({ data, status: "SUCCESS" })
7          : r({ status: "ERROR" }),
8      1500
9    );
10  });
Gotcha: while this pattern allows you to keep your UI responsive, you need to design how to handle errors. This hook doesn't return an error member, so regardless if you are using a Server Action (where you can't throw errors) or not, you might have to follow the pattern showcased here integrating errors in the action's state.
Why not leverage useActionState in your next React project? What do you think? Does it make it easier or not to to manage state, side effects and boilerplate in form operations?
Here are some resources that we'd recommend you explore:
Check out the code snippets and demo in webscopeio/examples/tree/main/use-action-state
Share post

Let’s stay connected

Do you want the latest and greatest from our blog straight to your inbox? Chuck us your email address and get informed.
You can unsubscribe any time. For more details, review our privacy policy