// app/actions.ts
"use server";
export async function createPost(prevState: { error?: string }, formData: FormData) {
const title = formData.get("title");
if (typeof title !== "string" || !title) return { error: "Title is required" };
await db.post.create({ data: { title } });
return {};
}
// NewPost.tsx
"use client";
const [state, formAction, pending] = useActionState(createPost, {});
// <form action={formAction}> ... {state.error} ...</form>
You pass a Server Action to a form's action prop. Progressive enhancement means the form works even before JavaScript loads.
How it works. On submit, React sends the FormData to the action. The action reads fields with formData.get("title"), validates them and saves.
Progressive enhancement. If a Server Component renders the form, it works before JavaScript loads, or with JavaScript turned off. The browser submits it as a normal POST. Once the page is hydrated (React has attached its event handlers), React submits it without a full page reload.
Feedback in Client Components:
useActionStatewraps the action. It gives you the last state the action returned, such as validation errors, and a pending flag.useFormStatuslets a child of the form, like a submit button, read whether the form is submitting.
- Pass the Server Action to
<form action>. - The action receives the form's
FormData. - Server-rendered forms can submit without JavaScript.
useActionStategives the returned state and a pending flag.useFormStatusreads the pending state from inside the form.
Read more