DEV Community

Cover image for How to add loading states with server action and useActionState hook in Nextjs 15 and React 19
Anjan Shomodder
Anjan Shomodder

Posted on

How to add loading states with server action and useActionState hook in Nextjs 15 and React 19

Display loading or managing states for form is much easier now with the new useActionState hook, whether you are using server actions or not. Let's see how to do it.

What is useActionState?

useActionState is a hook that allows you to update the state based on the form action. A Form action is a function triggered when a form is submitted. It could be a server action or a simple onSubmit function.

This hook is only available in React's Canary version. You don't need to install anything, if you are using a new nextjs app. You can install the canary version by running the following command:

npm install react@canary react-dom@canary
Enter fullscreen mode Exit fullscreen mode

We will create a simple login form that will trigger a server action to log in to the user.

I have an entire tutorial about the useActionState hook where I have explained more deeply. I went over not only loading states but also how to handle input reset problems and how to update the state from the useActionState hook.

How to use useActionState with server actions?

A server action is a function that runs only on the server. So, you get access to all the server resources like database, file system, etc.

// src/actions.js
'use server'

// Simulate a server action
const wait = (ms = 3000) => new Promise(resolve => setTimeout(resolve, ms))

export const login = async (prevState, formData) => {
  const email = formData.get('email')
  const password = formData.get('password')

  if (!email || !password) {
    return {
      error: 'Please fill in all fields',
      message: null,
    }
  }

  await wait()

  return {
    error: null,
    message: 'Log in successful',
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Adding a use server directive to the file will make all functions a server action.
  • login action will take two arguments: prevState and formData.
    • prevState is the return value of the previous function call if the function was called more than once. Otherwise, it is going to be the initial state, passed as the second argument to useActionState.
    • formData is the form data object that contains all the form field values.
  • Get all the form field values using the formData.get method.
  • If the email or password is empty, return an error message.
  • Wait for 3 seconds to simulate a server request.
  • Assuming a successful response and Return a success message.

Now, let's create a login form and use the useActionState hook to display loading states.

// src/LoginForm
'use client' // if using nextjs

const Page = () => {
  const [state, formAction, isPending] = useActionState(login, {
    message: null,
    error: null,
  })

  const { message, error } = state

  return (
    <div className='flex justify-center  items-center  h-screen'>
      <form action={formAction} className='w-96'>
        <label className='form-control w-full'>
          <div className='label'>
            <span className='label-text'>Email</span>
          </div>
          <input
            type='email'
            placeholder='Email'
            className='input input-bordered w-full '
            name='email'
          />
        </label>
        <label className='form-control w-full'>
          <div className='label'>
            <span className='label-text'>Password</span>
          </div>
          <input
            type='password'
            placeholder='Password'
            className='input input-bordered w-full '
            name='password'
          />
        </label>
        <div className='label'>
          <button disabled={isPending} className='btn btn-primary w-full'>
            {isPending ? (
              <span
                className='loading loading-ring
                        loading-md'
              ></span>
            ) : (
              Login
            )}
          </button>
        </div>

        {error && (
          <div role='alert' className='alert alert-error'>
            <span>Error! {error}</span>
          </div>
        )}

        {message && (
          <div role='alert' className='alert alert-success'>
            <span>{message}</span>
          </div>
        )}
      </form>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The useActionState hook takes two arguments: the action function and the initial state object.
  • The hook returns three values: state, formAction, and isPending.
    • state is the return value of the login action.
    • formAction is the function which you have passed to the useActionState hook.
    • isPending is a boolean value that is true when the form is submitted.
  • Pass the formAction function to the form's action attribute.
  • Disable the button when the form is submitted and show a loading spinner.
  • Show an error or success message based on the state.

That's it! You have successfully added loading states with server actions and the useActionState hook in Nextjs 15 and React 19.

If it was helpful, please drop a 🩷 and subscribe to my YouTube channel for more tutorials.

Top comments (0)