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>
);
}
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 };
}
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.
Top comments (0)