typescriptintermediate

Server Action Validation with Zod

Validate form data in server actions using Zod schemas with type-safe error handling.

typescript
'use server';

import { z } from 'zod';
import { revalidatePath } from 'next/cache';

const ContactSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  message: z.string().min(10, 'Message must be at least 10 characters').max(1000),
  category: z.enum(['general', 'support', 'feedback']),
});

type ContactForm = z.infer<typeof ContactSchema>;

interface ActionState {
  success: boolean;
  errors?: Record<string, string[]>;
  message?: string;
}

export async function submitContact(
  _prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const raw = {
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
    category: formData.get('category'),
  };

  const result = ContactSchema.safeParse(raw);

  if (!result.success) {
    return {
      success: false,
      errors: result.error.flatten().fieldErrors,
    };
  }

  // Process valid data
  await saveContact(result.data);
  revalidatePath('/contact');

  return {
    success: true,
    message: 'Message sent successfully!',
  };
}

// Client component usage:
// 'use client';
// import { useActionState } from 'react';
// const [state, formAction] = useActionState(submitContact, { success: false });
// <form action={formAction}>
//   {state.errors?.name && <p>{state.errors.name[0]}</p>}
// </form>

Use Cases

  • contact forms
  • registration
  • validated form submissions

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.