Building Forms with the New React Actions API (No onSubmit Needed)

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!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *