React 19 introduces Server Actions, enabling developers to declare async functions that execute securely on the server and can be invoked directly from client-side forms. This simplifies codebases by eliminating the need to write separate REST or GraphQL endpoints. Let's analyze how to use them with dynamic states.
How Server Actions Work
By marking a function with the "use server" directive at the top, React compiles it into a secure POST endpoint under the hood.
// src/app/actions.ts
"use server";
export async function joinWaitlist(prevState: any, formData: FormData) {
const email = formData.get("email");
if (!email || !email.toString().includes("@")) {
return { error: "Invalid email format." };
}
// Save directly to database
await saveToDb(email.toString());
return { success: true };
}
Integrating Action State inside Forms
React 19 provides the useActionState hook to manage form loading and response status easily inside Client Components:
// src/app/Form.tsx
"use client";
import { useActionState } from "react";
import { joinWaitlist } from "./actions";
export function Form() {
const [state, formAction, isPending] = useActionState(joinWaitlist, null);
return (
<form action={formAction} className="flex flex-col gap-4">
<input name="email" type="email" required className="border-4 border-black p-2" />
<button type="submit" disabled={isPending} className="bg-yellow-400 p-2 border-4 border-black">
{isPending ? "Submitting..." : "Join Waitlist"}
</button>
{state?.error && <p className="text-red-500">{state.error}</p>}
{state?.success && <p className="text-green-500">Subscribed successfully!</p>}
</form>
);
}
Security Considerations
Server Actions are public endpoints. Always sanitize inputs, validate permissions, and handle rate-limiting. For a review on server-side versus static next-on-pages setups, read Next.js Static Exports vs Edge SSR.
References & Citations
- React 19 Server Actions: React Official Blog
- Next.js Server Actions Guide: Next.js Docs
- Monorepo cache hashes: Monorepo Caching Guide