Diego Betto's Blog
React 19

July 24, 2024 · 5 min di lettura

React: useActionState and useFormStatus

Let's look at a few examples of how React 19's new useActionState and useFormStatus hooks work

Condividi:XLinkedInFacebookWhatsApp

React 19 introduces several interesting new features, including the useActionState hook. This experimental hook aims to simplify state and action management in React components, particularly for forms. In this article, we’ll explore useActionState by comparing it with the well-known useState hook, with code examples for both and a discussion of their pros and cons.

ℹ️ Note

to use these new hooks you need to switch to React 19, which is in beta. You can, for example, create a new project with vite with

npm create vite@latest

and then switch to the beta 19 versions of React with

npm i react@beta react-dom@beta

Comparing useActionState and useState

Let’s consider a simple login form with username and password fields.

Example with useState:

import React, { useState } from "react";

function App() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [response, setResponse] = useState(null);
  const [isPending, setIsPending] = useState(false);
  const [errors, setErrors] = useState(null);

  const handleSubmit = async (event) => {
    event.preventDefault();

    // Set the states before sending the request
    setResponse(null);
    setIsPending(true);
    setErrors(null);

    // Send the form data to the server, simulating a response delay
    await new Promise((resolve) => setTimeout(resolve, 2000));

    // Set the states after the response based on what we receive
    // obviously with real responses you'd need to check for errors
    setResponse("done!");
    setIsPending(false);
    setErrors(null);
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label>Username:</label>
        <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
        <label>Password:</label>
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
        <button type="submit">Log in</button>
      </form>

      <div>Response: {response}</div>
      <div>Status: {isPending ? "loading..." : "ready"}</div>
    </div>
  );
}

export default App;

Example with useActionState:

import React, { useActionState } from "react";

function App() {
  const sendRequest = async (prevState, formData) => {
    const username = formData.get("username");
    const password = formData.get("password");

    // simulate sending data to the server with a delay
    // here you could use a fetch request sending username and password
    await new Promise((resolve) => setTimeout(resolve, 2000));

    return { data: "done!", error: null };
    // in case of an error you'd instead have something like
    // return {data: prevState, error: 'Error!'}
  };

  const [userData, submitAction, isPending] = useActionState(sendRequest, {
    data: null,
    error: null,
  });

  return (
    <div>
      <form action={submitAction}>
        <label>Username:</label>
        <input type="text" name="username" />

        <label>Password:</label>
        <input type="password" name="password" />

        <button type="submit">Log in</button>
      </form>

      <div>Response: {userData.data}</div>
      <div>Status: {isPending ? "loading..." : "ready"}</div>
    </div>
  );
}

export default App;

What changes between the useState version and the useActionState version

Let’s look at how our code changes so we can understand the improvements.

  • We no longer need to manage the state of the individual input fields, all that matters is giving each one the correct name. So goodbye value and onChange => a cleaner form;
  • We no longer have to manage the form’s states, these are already handled by the new hook, and we’ll see we also get additional benefits from using useFormStatus;
  • Similarly, any errors generated by sending the data are exposed the same way.

The new useFormStatus hook

We also get another benefit. We can use the useFormStatus hook to read the form’s status and, for example, change the behavior of buttons or other elements in our form.

import { useFormStatus } from "react-dom"; // note: from `react-dom`, not `react`

const SubmitButton = () => {
  const { pending } = useFormStatus();

  return <button type="submit">{pending ? "Sending..." : "Submit"}</button>;
};

We can use this hook in any child element of the form, at any depth!
Let’s update the code to include this component too.

import React, { useActionState } from "react";
import { useFormStatus } from "react-dom";

const SubmitButton = () => {
  const { pending } = useFormStatus();

  return <button type="submit">{pending ? "Sending..." : "Submit"}</button>;
};

function App() {
  const sendRequest = async (prevState, formData) => {
    const username = formData.get("username");
    const password = formData.get("password");

    // simulate sending data to the server with a delay
    // here you could use a fetch request sending username and password
    await new Promise((resolve) => setTimeout(resolve, 2000));

    return { data: "done!", error: null };
    // in case of an error you'd instead have something like
    // return {data: prevState, error: 'Error!'}
  };

  const [userData, submitAction, isPending] = useActionState(sendRequest, {
    data: null,
    error: null,
  });

  return (
    <div>
      <form action={submitAction}>
        <label>Username:</label>
        <input type="text" name="username" />
        <label>Password:</label>
        <input type="password" name="password" />
        <SubmitButton />
      </form>

      <div>Response: {userData.data}</div>
      <div>Status: {isPending ? "loading..." : "ready"}</div>
    </div>
  );
}

export default App;

Now when we click the “Submit” button, while the form is loading data, we’ll see “Sending…”. Once loading finishes, the button goes back to showing “Submit”. Nice, right? This hook also exposes other data we can use, again at any level!

const { pending, data, method, action } = useFormStatus();

As you can see we have all the information available that can help us manage our form well. And it lets us compose our components more cleanly and independently, without too many props to pass down.

Conclusion

In both examples, the form’s state is managed. However, useActionState offers several advantages:

  • State and action integration: useActionState combines the form’s state and related actions into a single structure, simplifying state management and reducing boilerplate code;
  • Automatic form submission handling: useActionState provides a built-in submit action that automatically handles form submission and state updates based on the server’s response;
  • Less code: The useActionState example is more concise and requires less code to manage the form’s state and actions;

The useActionState hook in React 19 represents a significant step forward in form management, offering a simpler and more cohesive interface for state and actions. Although it’s still an experimental feature, its potential to simplify React form development is clear.

We’re still in beta, but if you want to try it out just install the beta versions of react and react-dom as we saw in the introduction.

Happy coding 😉

References

Condividi:XLinkedInFacebookWhatsApp