typescriptadvanced
Form with Server Action Validation
Build forms using Next.js server actions with server-side validation, error handling, and useActionState.
typescriptPress ⌘/Ctrl + Shift + C to copy
// app/contact/actions.ts
'use server';
interface FormState {
success: boolean;
errors: Record<string, string>;
message: string;
}
function validate(data: { name: string; email: string; message: string }) {
const errors: Record<string, string> = {};
if (!data.name || data.name.length < 2) {
errors.name = 'Name must be at least 2 characters';
}
if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.email = 'Valid email is required';
}
if (!data.message || data.message.length < 10) {
errors.message = 'Message must be at least 10 characters';
}
return errors;
}
export async function submitContact(
prevState: FormState,
formData: FormData,
): Promise<FormState> {
const data = {
name: formData.get('name') as string,
email: formData.get('email') as string,
message: formData.get('message') as string,
};
const errors = validate(data);
if (Object.keys(errors).length > 0) {
return { success: false, errors, message: 'Please fix the errors below' };
}
// Simulate saving
await new Promise((r) => setTimeout(r, 1000));
return { success: true, errors: {}, message: 'Message sent successfully!' };
}
// app/contact/page.tsx
'use client';
import { useActionState } from 'react';
import { submitContact } from './actions';
const initialState = { success: false, errors: {} as Record<string, string>, message: '' };
export default function ContactForm() {
const [state, action, isPending] = useActionState(submitContact, initialState);
return (
<form action={action} className="max-w-md mx-auto space-y-4">
<h1 className="text-2xl font-bold text-white">Contact Us</h1>
{state.message && (
<div className={`p-3 rounded-lg text-sm ${
state.success ? 'bg-green-900/50 text-green-300' : 'bg-red-900/50 text-red-300'
}`}>
{state.message}
</div>
)}
<Field name="name" label="Name" error={state.errors.name} />
<Field name="email" label="Email" type="email" error={state.errors.email} />
<div>
<label className="block text-sm text-gray-400 mb-1">Message</label>
<textarea
name="message"
rows={4}
className="w-full bg-[#111] border border-white/10 rounded-lg px-3 py-2
text-white focus:outline-none focus:border-blue-500"
/>
{state.errors.message && (
<p className="text-red-400 text-sm mt-1">{state.errors.message}</p>
)}
</div>
<button
type="submit"
disabled={isPending}
className="w-full py-3 bg-blue-600 text-white rounded-lg font-medium
hover:bg-blue-500 disabled:opacity-50 transition-colors"
>
{isPending ? 'Sending...' : 'Send Message'}
</button>
</form>
);
}
function Field({ name, label, type = 'text', error }: {
name: string; label: string; type?: string; error?: string;
}) {
return (
<div>
<label className="block text-sm text-gray-400 mb-1">{label}</label>
<input
name={name}
type={type}
className={`w-full bg-[#111] border rounded-lg px-3 py-2 text-white
focus:outline-none focus:border-blue-500
${error ? 'border-red-500' : 'border-white/10'}`}
/>
{error && <p className="text-red-400 text-sm mt-1">{error}</p>}
</div>
);
}Sponsored
Vercel
Use Cases
- Contact form submissions
- Server-validated forms
- Progressive enhancement forms
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptintermediate
Server Action with Form Validation
Next.js Server Action handling form submissions with validation, error messages, and redirect on success.
Best for: Blog post creation
#server-actions#forms
typescriptintermediate
Next.js Server Actions with Forms
Use Server Actions for form handling with validation, optimistic updates, and error handling.
Best for: Form submissions without API routes
#nextjs#server-actions
typescriptintermediate
Server Action Validation with Zod
Validate form data in server actions using Zod schemas with type-safe error handling.
Best for: contact forms
#nextjs#zod
typescriptintermediate
File Upload with Server Action
Handle file uploads using server actions with validation, size limits, and storage.
Best for: image uploads
#nextjs#file-upload