Trust is earned, not given

A different perspective

2024-02-13 · Projects

React.js, part 8: Server Actions — forms that talk to the server without a fetch

Part 8from the React.js series · 20 parts in all

A form submission used to be a small ceremony: prevent default, assemble a body, fetch, parse errors, branch on status, set state for each. Server Actions collapse that ceremony into a function the form can call directly — it runs on the server, and React drives the network and the pending UI for you.

An action is an async function marked as one

// app/actions.ts
'use server';

import { z } from 'zod';
import { db } from '@/lib/db';

const Schema = z.object({ email: z.string().email(), name: z.string().min(1) });

export async function subscribe(formData) {
  const parsed = Schema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) {
    return { ok: false, errors: parsed.error.flatten().fieldErrors };
  }
  await db.subscriber.create({ data: parsed.data });   // secret key, server-side
  return { ok: true };
}

The 'use server' directive is what makes this safe to import into a client component: the build extracts the function, gives the browser only a reference, and the body never ships. Everything above — the database call, the validation schema — stays on the server.

Wiring it to a form

'use client';

import { subscribe } from '@/app/actions';
import { useActionState } from 'react';

export function SignupForm() {
  const [state, action, pending] = useActionState(subscribe, { ok: false });

  return (
    <form action={action}>
      <input name="email" type="email" required />
      <input name="name" required />
      <button disabled={pending}>{pending ? 'Sending…' : 'Subscribe'}</button>
      {state.errors?.email && <p role="alert">{state.errors.email[0]}</p>}
    </form>
  );
}

The form works before hydration — no JavaScript required for the submit — and React upgrades it to a full fetch when the bundle loads. That progressive enhancement is free, which is the part that surprises people most.

What to keep in mind

Next: Context, and how to use it without re-rendering the world on every keystroke.