Published on

useActionState – React Hook for Simplified State Management

Authors
  • avatar
    Name
    Roy Bakker
    Twitter

When working with React 19, the introduction of the useActionState hook brings significant improvements to managing asynchronous operations within forms. Recently available in React's Canary and experimental channels, this hook is designed to update the state based on the result of form actions. This makes it easier to handle async functions directly within form components, enhancing the user experience.

To fully leverage useActionState, it's essential to use a framework that supports React Server Components. The hook simplifies state management by allowing state updates to be triggered by various async operations, creating a more seamless and responsive interface. This feature is a valuable addition for any React project aiming to implement progressive enhancement techniques effectively.

Reference

useActionState(actionFunction, startState, pageUrl?)

I use the useActionState hook to update component states whenever a form action is triggered. This hook takes an existing form action function and an initial state, returning a new action alongside the latest form state. This updated state is then accessible to the function provided.

Here’s a basic example:

import { useActionState } from 'react'

async function increment(previousState) {
  return previousState + 1
}

function StatefulForm() {
  const [state, formAction] = useActionState(increment, 0)

  return (
    <form>
      {state}
      <button formAction={formAction}>Increment</button>
    </form>
  )
}

In this setup, useActionState initializes with the state value and the action passed to the hook. The increment function here updates the state by adding one each time a form action is triggered.

The form state is derived from the value returned by the action during the last submission. If the form hasn't been submitted yet, the state remains as the initial value given.

When working with Server Actions, useActionState ensures that the server’s response to a form submission is visible even before hydration is done. This is particularly useful for dynamic pages where the response needs to be shown quickly.

Parameters

  1. Function (fn): This is the main function called when the form is submitted. It initially receives the previous state of the form, or the initialState if it's the first call. This is followed by the form's action arguments.
  2. Initial State (initialState): This value sets the initial state of the form. It can be any serializable value. Once the function is invoked, the initial state gets overwritten by the returned value of the function.
  3. Optional Permalink (permalink): This is a string containing a unique page URL that the form aims to modify. It comes in handy for pages with dynamic content, working alongside progressive enhancement. If the form submits before JavaScript loads, the browser navigates to this URL. Make sure the same form component is rendered on this new page to maintain the state.

Returns

useActionState yields an array that includes:

  • Current State: On the first render, it's the initialState. After invoking the action, it updates to the value returned by the action.
  • New Action: This new action can be used as the action prop for the form component or the formAction prop for any button within the form.

Caveats

  • With React Server Components: useActionState allows interactive forms even before client-side JavaScript executes. Without Server Components, it behaves like local state management within the component.
  • Function Parameters: The function used with useActionState expects an extra initial or previous state argument, which makes its usage distinct compared to directly using it as a form action without the hook.

By using useActionState, I can manage form actions and state changes efficiently, ensuring that state updates are handled seamlessly within my React components.

Usage

Using Data from a Form Action

At the top of your component, call useActionState to get the most recent return value of an action after a form is submitted.

import { useActionState } from 'react'
import { action } from './actions.js'

function MyComponent() {
  const [state, formAction] = useActionState(action, null)

  return <form action={formAction}>{/* ... */}</form>
}

The useActionState hook gives you an array with two elements:

  • State: Starts with the initial state you gave and updates to the return value of the action after submission.
  • Form Action: A new action that you can use as the action prop in the form.

When the form is submitted, the function you provided is called. Its return value becomes the new state of the form.

The action function gets the current state of the form as its first argument. During the first submission, this is the initial state. For later submissions, it’s the return value from the previous call. Remaining arguments stay the same as if useActionState was not in use.

function action(currentState, formData) {
  // ...
  return 'next state'
}

This approach helps manage state and actions in forms effectively, making React components more predictable and easier to handle during form submissions. This method also allows automatic tracking of loading states, ensuring your form component is always up-to-date with the backend API calls.

Troubleshooting

My action cannot read the submitted form data anymore

When I use useActionState, an extra parameter is added before the form data. This means that the form data becomes the second argument instead of the first. The first argument now represents the current state of the form.

function action(currentState, formData) {
  // Handle form data with new argument order
}

This change affects data handling in server actions, form submissions, and error management. Adjust the parameters to ensure the correct data is captured. Be mindful of these argument shifts to avoid issues with request processing and target actions.