Create forms using Form Actions and the ‘useFormStatus’ hook in Next.js

In this article, we will explore two latest React features: Form Actions and the useFormStatus hook, to create and manage forms in a better way. Note that, at the time of writing this article, these features are only available in React's Canary and Next.js App Router. Let's jump in!

Use Form Action

The introduction of Form Actions has made it easier for us to work with forms in React. Below is an example of a form using the Form Action:

'use client';

import { register } from '@server/actions/user';
import SubmitButton from '@component/SubmitButton';

export default function RegisterForm() {
  async function handleSubmit(formData: FormData) {
    const email = formData.get('email') as string;
    const password = formData.get('password') as string;

    if (!email || !password) {
      // Show missing required fields alert and return
      return console.log('Missing required fields');
    }
    const { error } = await register(email, password);
    // Show error message as alert and return
    if (error) return console.log(error.message);
  }

  return (
    <form action={handleSubmit}>
      <label htmlFor='email'>Email</label>
      <input type='email' id='email' name='email' />
      <label htmlFor='password'>Password</label>
      <input type='password' id='password' name='password' />
      <SubmitButton />
    </form>
  );
}

In the above code, we have a RegisterForm client component. We've made it a client component so that we can show data validation and submission error alerts.

Within the component, we have a handleSubmit function, and we return a form element with two input elements. This is an extended form element that allows us to pass a function to the action prop. In our case, we pass the handleSubmit function.

The handleSubmit function now works as a Form Action. By default, a Form Action is called with the formData argument, which contains the data of the submitted form.

Within the handleSubmit function, we access the form data by using the formData.get method and passing an argument to it. Note that the arguments must be the value of the name attributes provided in the input elements.

Finally, after extracting the data, we validate and call the register function with it. The register function is a server action that runs on the server and registers the user.

Use ‘useFormStatus’ hook

The other significant part of a form is the submit button. Below is an example of how we leverage the latest React features to create a submit button for our form:

'use client';

import { useFormStatus } from 'react-dom';

export function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type='submit' disabled={pending}>
      {pending ? 'Loading...' : 'Submit'}
    </button>
  );
}

In the above code, we have a SubmitButton client component. We made it a client component so that we can use the useFormStatus hook. Within the component, we destructure the result of the useFormStatus hook and get the pending property out of it.

The value of the pending property is a boolean, which is true when the form submission starts and changes to false when the form submission ends. We use the pending property to disable the button during the submission and change the button text based on the status.

Note that for the useFormStatus hook to work, we must put the SubmitButton component inside the form element.

That's it! This approach eliminates the need to manage form and submission status states by leveraging the latest React features. However, in an advanced form with features like checkboxes, we still need to use states to get the value of the selected elements. That is fine, though, as we can combine both approaches and get the best of both worlds.

Share this article with your friends

Copy URL

Elevate your JavaScript and freelance journey

Supercharge your JavaScript skills and freelance career. Subscribe now for expert tips and insights!