LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 3418 min read

Forms & Data Mutations

Build a complete, validated form with pending and error states using Server Actions and the useActionState hook.

Server-Side Validation

Since a Server Action always runs on the server, it's the right place to validate input — never trust that data reaching it is well-formed, even if you also validate in the browser for a snappier UX.

'use server';
export async function createPost(prevState, formData) {
const title = formData.get('title');
if (!title || title.length < 3) {
return { error: 'Title must be at least 3 characters.' };
}
await db.post.create({ data: { title } });
revalidatePath('/blog');
return { error: null };
}

Showing a Pending State with useFormStatus

The useFormStatus hook, used inside a component nested in the form, reports whether that form is currently submitting — perfect for disabling a submit button or showing a spinner.

'use client';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Saving…' : 'Save'}
</button>
);
}

Returning Errors with useActionState

useActionState wraps a Server Action so the form can hold onto whatever the action returns — like a validation error — and re-render with it, without any manual state management on your part.

'use client';
import { useActionState } from 'react';
import { createPost } from '../actions';
export default function NewPostForm() {
const [state, formAction] = useActionState(createPost, { error: null });
return (
<form action={formAction}>
<input name="title" placeholder="Post title" />
{state.error && <p className="error">{state.error}</p>}
<SubmitButton />
</form>
);
}

A Complete Example

Put together, a Server Action plus useActionState plus useFormStatus gives you server-validated, progressively-enhanced forms with proper pending and error UI — all without a separate client-side state management layer for the form itself.

User submits form
↓
useFormStatus shows pending state
↓
Server Action validates and mutates
↓
Action returns { error } or success
↓
useActionState re-renders form with the result

Common Beginner Mistakes

Only validating on the client

Client-side validation is for UX; a Server Action must independently validate, since it can be called directly, bypassing any client-side checks.

Calling useFormStatus in the same component that renders the <form>

It only reports status for the nearest parent <form> — it must be called from a component nested inside the form, not the form's own component.

FAQs

It's not required, but it's common and recommended for anything beyond trivial checks — it keeps validation rules declarative and reusable between client and server.

Yes — the returned state object can carry any shape of data the action returns, including a success flag or a redirect instruction.

Summary

Server Actions combined with useFormStatus and useActionState give you complete, validated forms without a separate client-state library. Next, you'll learn about Middleware — code that runs before a request even reaches your routes.

Next Lesson →

Middleware