Forms and Mutations
Server Actions, form handling, validation, and progressive enhancement.
Overview
Server Actions enable form submissions without API routes.
Key Concepts
- Server Actions — Functions that run on the server
- Form Actions — Native form submission with actions
- Validation — Server-side validation with errors
- Optimistic Updates — UI updates before server response
- Progressive Enhancement — Forms work without JavaScript
Code Examples
// app/actions.js
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
if (!title) {
return { error: 'Title is required' };
}
await db.posts.create({ data: { title, content } });
revalidatePath('/posts');
}
// app/posts/new/page.js
import { createPost } from '../actions';
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" />
<button type="submit">Create Post</button>
</form>
);
}
// With validation and error handling
export async function createPostWithValidation(formData) {
const title = formData.get('title');
const errors = {};
if (!title) errors.title = 'Title is required';
if (title && title.length < 3) errors.title = 'Title must be 3+ characters';
if (Object.keys(errors).length > 0) {
return { errors };
}
await db.posts.create({ data: { title } });
revalidatePath('/posts');
redirect('/posts');
}
// Client component with useActionState
'use client';
import { useActionState } from 'react';
function PostForm() {
const [state, formAction, pending] = useActionState(createPostWithValidation, null);
return (
<form action={formAction}>
<input name="title" />
{state?.errors?.title && <span>{state.errors.title}</span>}
<button disabled={pending}>Submit</button>
</form>
);
}
Practice
Build a form with validation, error display, and optimistic updates.