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

bookercodes

Alex Booker

Posted on November 16, 2024

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

When using a form, the useActionState hook simplifies the process of capturing form values and passing them to a server action as FormData.

useActionState also 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, so you should import useActionState moving forward.

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
bookercodes
Alex Booker

Posted on November 16, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About