Quick Start
Forms are undoubtedly among the most commonly used fundamental building blocks on the web. Sign up, log in, subscribe, add payment method... We are constantly filling these out. So, how can we manage these critically important forms more effectively in our applications? What do the form actions introduced in React 19 offer us?
Traditional Method
The previous traditional method for handling form submissions in React required several manual steps (like creating a new useState
for each input). When a user submitted a form, we typically managed the process as follows:
import { useState } from "react";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const NewsletterForm = () => {
const [email, setEmail] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
await sleep(3000);
setIsSubmitting(false);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Submitting..." : "Subscribe"}
</button>
</form>
);
};
export default NewsletterForm;
- We would create a
<form>
element and bind a custom function (likehandleSubmit
) to theonSubmit
event to run when the form was submitted. - Inside
handleSubmit
, the first step is typically to callevent.preventDefault()
. This prevents the browser from reloading the page. - A separate state (using
useState
) would be defined for each input field in the form (e.g., email). - An
onChange
event listener would be added to each input field to capture the user's input and update the corresponding state in real-time. - During form submission (e.g., while an API request is in progress), we also had to manually manage an additional state, like isSubmitting, to update the user interface (e.g., disable the button, display "Submitting..." text).
React v19: useActionState
React 19 significantly simplifies form management with the useActionState
hook. This hook allows us to handle form submissions, state management, and pending states with less code and in a more declarative manner.
import { useActionState } from "react";
type NewsletterFormState = {
email: string;
};
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const NewsletterForm = () => {
const handleSubmit = async (
_previousState: NewsletterFormState,
formData: FormData
) => {
const email = formData.get("email");
await sleep(3000);
return { email: email as string };
};
const initialState: NewsletterFormState = { email: "" };
const [state, formAction, isPending] = useActionState(
handleSubmit,
initialState
);
return (
<form action={formAction}>
{JSON.stringify(state)}
<input name="email" type="email" />
<button type="submit" disabled={isPending}>
{isPending ? "Submitting..." : "Subscribe"}
</button>
</form>
);
};
export default NewsletterForm;
As seen in the code above, by passing the action={formAction}
prop to the <form>
element, we delegate the form management mechanism to the useActionState
hook. The key benefits of this approach are:
- Simplified State Management: There's no need to define separate
useState
for each input and update them withonChange
. When the form is submitted, the values of inputs with a name attribute are automatically passed to thehandleSubmit
function via aFormData
object. - Automatic Pending State: The hook automatically provides an isPending boolean value which is true while the
handleSubmit
function is running (duringawait sleep(3000)
). This eliminates the need to maintain an extraisSubmitting
state. - Less Code: The
onSubmit prop
,event.preventDefault()
, andonChange
handlers for inputs are no longer needed. - State Updates: The value returned by the action function (
handleSubmit
) (e.g., { email: ... }) is automatically captured by the hook and set as the new state value.
Thanks.