Form Handling with useActionState, useFormStatus & Zod Validation0%
On-Demand Cache Revalidation with revalidatePath and revalidateTag

Form Handling with useActionState, useFormStatus & Zod Validation

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Form Handling with useActionState, useFormStatus & Zod Validation

Building professional forms in Next.js 15 requires combining React 19's useActionState (for managing action state and error responses), useFormStatus (for loading and pending states), and Zod (for type-safe schema validation).


1. The React 19 / Next.js 15 Form Stack

  • useActionState: Hook that wraps a Server Action to track return values, validation errors, and pending state across submissions.
  • useFormStatus: Hook that provides the pending status of a parent <form>, allowing buttons to display spinners during submission.
  • Zod Validation: Validates form fields on the server before database mutation.

2. Server Action with Zod Validation

TypeScript
// app/actions/auth-actions.ts
'use server';
 
import { z } from 'zod';
 
const signupSchema = z.object({
email: z.string().email('Invalid email address format'),
password: z.string().min(8, 'Password must be at least 8 characters long')
});
 
export type ActionState = {
errors?: {
email?: string[];
password?: string[];
};
message?: string;
};
 
export async function signupUser(prevState: ActionState, formData: FormData): Promise<ActionState> {
const result = signupSchema.safeParse({
email: formData.get('email'),
password: formData.get('password')
});
 
if (!result.success) {
return {
errors: result.error.flatten().fieldErrors
};
}
 
// Database registration logic...
return { message: 'Account successfully registered!' };
}

3. The Client Form Component with useActionState

TSX
// components/SignupForm.tsx
'use client';
 
import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
import { signupUser, type ActionState } from '@/app/actions/auth-actions';
 
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className="w-full bg-blue-600 text-white py-2 rounded disabled:bg-slate-400"
>
{pending ? 'Submitting...' : 'Sign Up'}
</button>
);
}
 
export default function SignupForm() {
const initialState: ActionState = {};
const [state, formAction] = useActionState(signupUser, initialState);
 
return (
<form action={formAction} className="max-w-md mx-auto space-y-4">
{state.message && <p className="text-green-600 font-medium">{state.message}</p>}
 
<div>
<label className="block text-sm font-medium">Email</label>
<input name="email" type="email" className="w-full border p-2 rounded" />
{state.errors?.email && (
<p className="text-red-500 text-sm mt-1">{state.errors.email[0]}</p>
)}
</div>
 
<div>
<label className="block text-sm font-medium">Password</label>
<input name="password" type="password" className="w-full border p-2 rounded" />
{state.errors?.password && (
<p className="text-red-500 text-sm mt-1">{state.errors.password[0]}</p>
)}
</div>
 
<SubmitButton />
</form>
);
}

Interactive Quiz

Multiple Choice Questions

Question 1
Which React 19 hook replaces the experimental useFormState in Next.js 15 for managing server action return states and errors?
Question 2
Where must the useFormStatus hook be called to accurately detect if a form submission is pending?
Question 3
What method on a Zod schema validates data without throwing exceptions, returning an object with { success: true, data } or { success: false, error }?
Question 4
What is the first argument passed to a Server Action when wired through useActionState?
Question 5
Why is server-side validation with Zod necessary even if the client form includes HTML5 required and type="email" attributes?

Next Lesson

On-Demand Cache Revalidation with revalidatePath and revalidateTag

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext