Server action form

PRISM exports a loader (runs on GET) and an action (runs on POST). After a successful action, PRISM reruns the loader so the page renders with fresh data — Remix-style.

Tip
An action can return either a plain object (re-renders via actionData) or a 303 Response to redirect. Prefer the redirect for successful mutations: it makes the URL refreshable and prevents double-submit on F5.

Live output

count: 0

src/app/examples/server-action-form/page.tsxTSX
// src/app/examples/server-action-form/page.tsx
const __state = (globalThis as any).__demoCounter ??= { count: 0, lastNote: null };

export async function loader() {
  return { count: __state.count, lastNote: __state.lastNote };
}

export async function action({ request }: ActionArgs) {
  const form = await request.formData();
  const op = String(form.get("op") ?? "");
  const note = String(form.get("note") ?? "").slice(0, 60);
  if (op === "inc") __state.count++;
  if (op === "dec") __state.count--;
  if (op === "reset") { __state.count = 0; __state.lastNote = null; }
  if (note) __state.lastNote = note;
  return { ok: true, op };
}

export default function Page({ data, actionData }) {
  return (
    <form method="post">
      <p>count: {data.count}</p>
      {data.lastNote ? <p>last note: {data.lastNote}</p> : null}
      <button type="submit" name="op" value="inc">+1</button>
      <button type="submit" name="op" value="dec">−1</button>
      <button type="submit" name="op" value="reset">reset</button>
      <input name="note" placeholder="optional note…" />
      {actionData?.ok ? <p>action ran: {actionData.op} ✓</p> : null}
    </form>
  );
}