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)