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
// 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>
);
}