React's useActionState Explained with a Real-World Example


useActionState is a React hook that simplifies managing form submissions and their state. It’s particularly useful in React applications (especially with frameworks like Next.js) that use server actions or progressive enhancement. It allows you to:
Track form state: Keep track of the result of a form submission (e.g., success or error messages).
Handle form actions: Wrap an existing action (like a server function) to manage form submissions.
Show loading states: Provide feedback (e.g., "Loading...") while the action is processing.
Example: Create Account Form
Here’s a simple form that uses
useActionState
to validate and submit user input, show relevant errors, and reset the form on success.const [state, formAction, isPending] = useActionState(submitForm, initialState);
state
: Current status of the form (success/error).formAction
: Hook-enhanced action used as the form'saction
.isPending
: Boolean to show a loading state during submission.
Upon submission:
To simulate a backend check for existing users:
If the name is
"john007"
or the email is"
john@gmail.com
"
, the form returns a "user already exists" error.On successful submission, a thank-you message is displayed and the form is reset
Profile.js
import { useState, useEffect } from "react";
import { useActionState } from "react";
import { submitForm } from "./utils";
const initialState = {
status: "",
errorField: "",
message: "",
};
export const FeedbackForm = () => {
const [inputName, setInputName] = useState("");
const [inputEmail, setInputEmail] = useState("");
const [state, formAction, isPending] = useActionState(
submitForm,
initialState
);
useEffect(() => {
if (state.status === "success") {
setInputName("");
setInputEmail("");
}
}, [state.status]);
// Validate form to enable/disable submit button
const isFormValid = inputName.trim() !== "" && inputEmail.trim() !== "";
return (
<div className="feedback-form-container">
<h2 className="feedback-form-title">Create Your Account</h2>
<form action={formAction} className="feedback-form">
<div className="form-group">
<label htmlFor="name" className="form-label">
Name
</label>
<input
type="text"
name="name"
id="name"
className="form-input"
placeholder="Your name"
value={inputName}
onChange={(e) => setInputName(e.target.value)}
disabled={isPending}
/>
{state.status === "error" && state.errorField === "name" && (
<p className="form-error">{state.message}</p>
)}
</div>
<div className="form-group">
<label htmlFor="email" className="form-label">
Email
</label>
<input
type="email"
name="email"
id="email"
className="form-input"
placeholder="Your email"
value={inputEmail}
onChange={(e) => setInputEmail(e.target.value)}
disabled={isPending}
/>
{state.status === "error" && state.errorField === "email" && (
<p className="form-error">{state.message}</p>
)}
</div>
<button
type="submit"
className={`form-button ${
isPending || !isFormValid ? "button-disabled" : ""
}`}
disabled={isPending || !isFormValid}
>
{isPending ? "Creating..." : "Create Account"}
</button>
{state.status === "success" && (
<p className="form-success">{state.message}</p>
)}
</form>
</div>
);
};
utils/submitFeedback
export const submitForm = async (prevState, formData) => {
const name = formData.get("name")?.trim();
const email = formData.get("email")?.trim();
await new Promise((res) => setTimeout(res, 1000));
if (name === "john007") {
return {
status: "error",
errorField: "name",
message: "username already exist",
};
}
if (email === "john@gmail.com") {
return {
status: "error",
errorField: "email",
message: "Email already exist",
};
}
return {
status: "success",
message: `Thanks, ${name}! Your Account has been created.`,
};
};
Output
Subscribe to my newsletter
Read articles from AKhil kumar Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
