DEV Community

Mahmood Hassan Rameem
Mahmood Hassan Rameem

Posted on

React Form with "useActionState" Hook

πŸ“˜ React Forms with useActionState

πŸ”Ή Why Use useActionState?

Traditionally, handling forms in React required:

  • Creating multiple useState hooks for inputs.
  • Writing an onSubmit function with event.preventDefault().
  • Managing pending/loading states manually.

This approach works, but it gets repetitive and messy when forms grow.

React 19 introduced the useActionState hook to simplify this:

  • βœ… No need for useState per input field.
  • βœ… Automatically handles pending state (loading).
  • βœ… Works seamlessly with Server Actions in Next.js App Router.
  • βœ… Cleaner, more declarative way to manage form submissions.

In short:
πŸ‘‰ useActionState = a modern replacement for onSubmit + useState.


πŸ”Ή Syntax

const [state, action, isPending] = useActionState(fn, initialState);
Enter fullscreen mode Exit fullscreen mode
  • fn β†’ async function that runs on form submit.
  • initialState β†’ default value before submission.
  • state β†’ result returned from fn.
  • action β†’ handler you pass to <form action={action}>.
  • isPending β†’ boolean, true while form is submitting.

πŸ”Ή Basic Example

"use client";

import { useActionState } from "react";

async function submitForm(prevState, formData) {
  const name = formData.get("name");
  return `Hello, ${name}!`;
}

export default function SimpleForm() {
  const [message, formAction, isPending] = useActionState(submitForm, "");

  return (
    <form action={formAction}>
      <input type="text" name="name" placeholder="Enter your name" />
      <button type="submit" disabled={isPending}>
        {isPending ? "Submitting..." : "Submit"}
      </button>
      {message && <p>{message}</p>}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή Multiple Input Example

"use client";

import { useActionState } from "react";

async function registerUser(prevState, formData) {
  const username = formData.get("username");
  const email = formData.get("email");
  const password = formData.get("password");

  if (!email.includes("@")) {
    return "❌ Invalid email address!";
  }

  return `βœ… User ${username} registered successfully!`;
}

export default function RegisterForm() {
  const [result, formAction, isPending] = useActionState(registerUser, "");

  return (
    <form action={formAction}>
      <input type="text" name="username" placeholder="Username" required />
      <br />
      <input type="email" name="email" placeholder="Email" required />
      <br />
      <input type="password" name="password" placeholder="Password" required />
      <br />
      <button type="submit" disabled={isPending}>
        {isPending ? "Registering..." : "Register"}
      </button>
      {result && <p>{result}</p>}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή Checkbox, Radio, and Select Example

"use client";

import { useActionState } from "react";

async function handleForm(prevState, formData) {
  return {
    gender: formData.get("gender"),
    agree: formData.get("agree") === "on",
    country: formData.get("country"),
  };
}

export default function AdvancedForm() {
  const [data, formAction, isPending] = useActionState(handleForm, null);

  return (
    <form action={formAction}>
      {/* Radio */}
      <label>
        <input type="radio" name="gender" value="Male" /> Male
      </label>
      <label>
        <input type="radio" name="gender" value="Female" /> Female
      </label>
      <br />

      {/* Checkbox */}
      <label>
        <input type="checkbox" name="agree" /> I agree to the terms
      </label>
      <br />

      {/* Select */}
      <select name="country" defaultValue="">
        <option value="">--Select Country--</option>
        <option value="Bangladesh">Bangladesh</option>
        <option value="India">India</option>
        <option value="USA">USA</option>
      </select>
      <br />

      <button type="submit" disabled={isPending}>
        {isPending ? "Processing..." : "Submit"}
      </button>

      {data && (
        <pre style={{ marginTop: "10px" }}>
          {JSON.stringify(data, null, 2)}
        </pre>
      )}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή Key Benefits of useActionState

  • βœ… Removes boilerplate (useState + onSubmit).
  • βœ… Cleaner, declarative form handling.
  • βœ… Automatic loading state (isPending).
  • βœ… Works with client and server actions.
  • βœ… Future-proof with React 19 + Next.js App Router.

⚑ In short:
useActionState makes forms simpler, cleaner, and ready for modern React apps.


Do you also want me to include a Next.js Server Action version (where the action runs fully on the server instead of client)?

Top comments (0)