Select Page

useActionState React Hook

by | Mar 13, 2024

The React team is updating a hook called useFormState because it caused confusion and wasn’t just for forms. They’re making these changes:

  1. Renaming it to useActionState.
  2. Adding a pending state to the returned values.
  3. Moving the hook from react-dom to the react package.

Now, other renderers like React Native can use it too. There are more benefits, like a “partial progressive enhancement” feature. But there are also new things to be careful about, which are documented in the PR.

This change aims to clear up confusion and enhance the functionality of the useFormState hook.

useActionState Hook

The useFormState hook, previously only available in the ReactDOM package, might confuse users into thinking it’s solely for managing form-related states. However, unlike useFormStatus which deals with form statuses, useFormState actually tracks the state of actions passed to it, regardless of whether they’re related to forms or not. Therefore, expecting useFormState to provide a pending state for forms would be misleading.

To address this, the hook is renamed to useActionState and now includes a pending state value. Additionally, it’s moved to the ‘react’ package to emphasize its broader usage beyond ReactDOM. This update allows users to track the state and pending status of any action, not just those related to forms.

How to use the useActionState hook

import { useActionState } from "react";

function Form({ formAction }) {
  const [state, action, isPending] = useActionState(formAction);

  return (
    <form action={action}>
      <input type="email" name="email" disabled={isPending} />
      <button type="submit" disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </form>
  );
}

You can also use useActionState outside of a element, providing flexibility in its usage:

import { useActionState, useRef } from "react";

function Form({ someAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(someAction);

  async function handleSubmit() {
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}

This hook provides automatic tracking of action states and is particularly useful for render-specific elements like ReactDOM

elements and Server Actions. It enhances user experience by supporting partial progressive enhancement and full progressive enhancement with server actions, even without JavaScript on the client.

Some key points to remember when using useActionState

  1. Additional State Update: The hook always sets the pending state at the beginning of the first chained action. This may result in an additional state update similar to useTransition. Future improvements may optimize this behavior for cases where the pending state is not accessed.
  2. Pending State is for the Action, not the Handler: The pending state starts when the action is dispatched and reverts back after all actions and transitions have settled. This means it doesn’t represent any actions or synchronous work performed before dispatching the action returned by useActionState. Be mindful of this difference to avoid confusion.

To illustrate, consider this example:

import { useActionState, useRef } from "react";

function Form({ someAction, someOtherAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(someAction);

  async function handleSubmit() {
    await someOtherAction();

    // The pending state does not start until this call.
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}

Since the pending state is tied to the action, it only changes when the action is dispatched. To address this, you can either include the other function call inside the action or wrap both actions in a transition for greater control over the pending state.

Here’s how you can implement these solutions:

import { useActionState, useRef } from "react";

function Form({ someAction, someOtherAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(async (data) => {
    // Pending state is true already.
    await someOtherAction();
    return someAction(data);
  });

  async function handleSubmit() {
    // The pending state starts at this call.
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}

Alternatively, you can wrap both actions in a transition to manage the pending state:

import { useActionState, useTransition, useRef } from "react";

function Form({ someAction, someOtherAction }) {
  const ref = useRef(null);
  const [isPending, startTransition] = useTransition();
  const [state, action] = useActionState(someAction);

  async function handleSubmit() {
    startTransition(async () => {
      await someOtherAction();
      await action({ email: ref.current.value });
    });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}

Using a transition ensures smoother handling of pending states and offers better control over when the pending state starts and ends.

Lastly, it’s worth noting that using useOptimistic is preferred over using useTransition directly. This provides a similar technique for managing pending states while maintaining optimal performance. You can explore this approach further for more sophisticated handling of pending states in your application.

Conslusion

In summary, the useActionState hook offers enhanced functionality for tracking the state and pending status of actions. By understanding its behavior and considering the nuances of pending states, you can leverage this hook effectively to improve user experience and streamline your application’s logic.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

Looking For Something?

Follow Us

Related Articles

Understanding Layouts in React

Understanding Layouts in React

If you're someone who works with React, you might think you know what a layout is. But, do you really? React, a popular JavaScript library for building user interfaces, employs the concept of layouts to organize and structure web applications. Despite its widespread...

useSyncExternalStore React API

useSyncExternalStore React API

You might have heard about a new tool called useSyncExternalStore() in React 18. It helps connect your React app to outside data sources. Usually, it's used by fancy internal tools like Redux to manage state. The official documentation explains that...

Subscribe To Our Newsletter

Subscribe To Our Newsletter

Join our mailing list to receive the latest news and updates from our team.

You have Successfully Subscribed!