π 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)