React’s new Actions API is one of the most exciting changes introduced in React 19 and surfaced early through Next.js 15 Alpha. This feature shifts how we handle form submissions — from traditional client-side onSubmit
handlers to a server-first model, powered by function calls that run directly on the server.
If you’ve ever thought “Why am I sending data to the client only to immediately send it back to the server?”, then React Actions are for you.
🔄 The Old Way: Client-Side onSubmit
with Fetch
Before React Actions, most forms in React apps followed this classic pattern:
function ContactForm() {
const handleSubmit = async (event) => {
event.preventDefault();
const data = new FormData(event.target);
await fetch("/api/contact", {
method: "POST",
body: data,
});
};
return (
<form onSubmit={handleSubmit}>
<input name="email" />
<textarea name="message" />
<button type="submit">Send</button>
</form>
);
}
This approach requires a lot of boilerplate: preventing default behavior, manually fetching, and managing loading states.
🚀 The New Way: Server-First with React Actions
With the new Actions API, form handling becomes as simple as calling a server function — no manual fetch
, no onSubmit
, no state juggling.
// app/actions.ts
'use server';
export async function sendContact(formData: FormData) {
const email = formData.get('email');
const message = formData.get('message');
// Save to DB or send email here
}
// app/contact/page.tsx
import { sendContact } from '../actions';
export default function ContactPage() {
return (
<form action={sendContact}>
<input name="email" />
<textarea name="message" />
<button type="submit">Send</button>
</form>
);
}
That’s it. No onSubmit
, no extra fetch logic. React handles everything under the hood: serializing the form, calling the server function, and even streaming back the result.
⚠️ Validation Example
You can even throw errors on the server and handle them gracefully in the UI:
// app/actions.ts
'use server';
export async function sendContact(formData: FormData) {
const email = formData.get('email');
if (!email || !email.toString().includes('@')) {
throw new Error('Invalid email address');
}
// Process form...
}
React will pause rendering with Suspense and resume once the action completes, updating just the relevant part of the UI.
🧠 Why This Matters
- ✅ Cleaner code: No more handling
event.preventDefault()
or writing fetch calls. - ✅ Server-first: You can do validation, DB writes, and logic right where it belongs.
- ✅ Progressive enhancement: You can still add client-side validation when needed.
- ✅ Streaming: Combine with React 19’s Suspense features for smooth UX.
🏁 Final Thoughts
React Actions are a game changer. They let us write cleaner, more secure, and server-focused code for forms — while still keeping the flexibility of the client where needed. If you’re using Next.js 15 Alpha or React 19+, it’s the perfect time to start experimenting with this pattern.
Let me know if you’d like a real-world example (like auth or contact forms), and I’ll add that next!