π React Forms with useActionState
πΉ Why Use useActionState
?
Traditionally, handling forms in React required:
- Creating multiple
useState
hooks for inputs. - Writing an
onSubmit
function withevent.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);
-
fn
β async function that runs on form submit. -
initialState
β default value before submission. -
state
β result returned fromfn
. -
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> ); }
πΉ 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> ); }
πΉ 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> ); }
πΉ 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)