DEV Community

Alex Booker
Alex Booker

Posted on • Edited on

5

Learn useActionState quickly with an example (Next.js 15)

When using a form, the useActionState manages state by automatically updating a state variable with the value returned from the server action. This is particularly helpful for rendering input field validation errors, as shown in the example below using Zod.

form.tsx:

"use client";

import { useActionState } from "react";
import { signUp } from "../actions";

export default function SignUp() {
  const [state, action] = useActionState(signUp, {});

  return (
    <form action={action}>
      <div>
        <label htmlFor="username">Username:</label>
        <input
          type="text"
          id="username"
          name="username"
          defaultValue={state.username}
          required
        />
        {state.errors?.username && (
          <p className="text-sm text-red-500">{state.errors.username}</p>
        )}
      </div>

      <div>
        <label htmlFor="password">Password:</label>
        <input
          type="password"
          id="password"
          name="password"
          defaultValue={state.password}
        />
        {state.errors?.password && (
          <p className="text-sm text-red-500">{state.errors.password}</p>
        )}
      </div>
      <input type="submit" value="Sign Up" />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

actions.ts:

"use server";

import { z } from "zod";

const SignUpSchema = z.object({
  username: z.string(),
  password: z
    .string()
    .min(8, { message: "Be at least 8 characters long" })
    .regex(/[a-zA-Z]/, { message: "Contain at least one letter." })
    .regex(/[0-9]/, { message: "Contain at least one number." })
    .regex(/[^a-zA-Z0-9]/, {
      message: "Contain at least one special character.",
    })
    .trim(),
});

export type SignUpActionState = {
  username?: string;
  password?: string;
  errors?: {
    username?: string[];
    password?: string[];
  };
};

export async function signUp(
  _prevState: SignUpActionState,
  form: FormData
): Promise<SignUpActionState> {
  const username = form.get("username") as string;
  const password = form.get("password") as string;

  const validatedFields = SignUpSchema.safeParse({
    username,
    password,
  });

  if (!validatedFields.success) {
    return {
      username,
      password,
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }

  // process validated form inputs here

  return { username, password };
}
Enter fullscreen mode Exit fullscreen mode

useActionState also returns an isPending property (example) that indicates whether the server action's promise is still resolving.

Reference isPending to temporarily disable form elements, such as a submit button, to prevent users from clicking it multiple times in quick succession before the ongoing action has completed.

useActionState vs useFormAction and useFormStatus

If you’re familiar with useFormAction and useFormStatus, you’ll find useActionState quite similar.

Essentially, it combines the functionality of both hooks and is renamed to reflect that server actions aren't just for forms (you can also use useActionState with buttons and other elements.)

Keep in mind that useFormStatus is deprecated as of Next.js 15 and you should import useActionState moving forward.

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay