DEV3 Technologies home

DEV3 Developer Forum

How do I use Server Actions in Next.js to handle a form submit?

Next.js helpSolved0 views
dev3 ·

I have a contact form in the App Router and I would like to avoid writing an API route. How do I write a Server Action, show validation errors and a pending state?

#nextjs#server-actions#forms

0

1 reply

dev3 · Accepted answer

A Server Action is an async function marked "use server" that runs on the server and can be passed to a form's action.

// app/contact/actions.ts
"use server";
import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  message: z.string().min(10),
});

export type State = { ok?: boolean; errors?: Record<string, string[]> };

export async function sendMessage(_prev: State, formData: FormData): Promise<State> {
  const parsed = schema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) {
    return { errors: parsed.error.flatten().fieldErrors };
  }
  // save to DB or send mail here
  return { ok: true };
}
// app/contact/form.tsx
"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
import { sendMessage } from "./actions";

function Submit() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? "Sending..." : "Send"}</button>;
}

export default function Form() {
  const [state, action] = useActionState(sendMessage, {});
  return (
    <form action={action}>
      <input name="email" />
      {state.errors?.email && <p>{state.errors.email[0]}</p>}
      <textarea name="message" />
      <Submit />
      {state.ok && <p>Sent.</p>}
    </form>
  );
}

Notes

  • useActionState is the React 19 name (it was useFormState in React 18 canaries).
  • Always validate on the server and check authorisation inside the action: it is a public POST endpoint.
  • The form works without JavaScript when the action is passed directly (progressive enhancement).
  • Call revalidatePath or revalidateTag after mutating data so cached pages update.

Pitfall: defining zod schemas with inline .refine inside a "use server" file can break compilation in some setups. Keep schemas in a separate module and import them.

0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.