The Back Story
I've been writing about forms and data management in React for a long time. As many of us know, handling forms in React has always been a pain. You have to manage state, pass it to inputs, handle change events, and then gather all that state to use your form data. Even for something as simple as a text field, you need a state variable and a change handler.
Sure, we've gotten used to this pattern — but honestly, it's a lot more work compared to plain HTML forms.
Enter React 19
Recently, React launched a new stable version that introduced several new hooks. Among the updates, one that stands out is the useActionState
hook. And it's a game changer — especially for forms.
With useActionState
, you no longer need to manually manage state or write a change handler just to get the value of a text field.
Let’s look at a simple example: a user details form. It has a few input fields, and when the user clicks the submit button, we validate the input and send the data to the server.
export default function Form() {
return (
<div className="form-cont">
<form>
<input
type="text"
placeholder="Username"
name="user"
aria-label="Username"
/>
<input
type="password"
placeholder="Password"
name="password"
aria-label="Password"
/>
<select name="select" defaultValue="">
<option value="" disabled>
-- Select a fruit --
</option>
<option value="banana">Banana</option>
<option value="apple">Apple</option>
<option value="orange">Orange</option>
</select>
<fieldset>
<legend>Choose a fruit (radio)</legend>
<label>
<input type="radio" name="fruit" value="orange" />
Orange
</label>
<label>
<input type="radio" name="fruit" value="apple" />
Apple
</label>
<label>
<input type="radio" name="fruit" value="banana" />
Banana
</label>
</fieldset>
<fieldset>
<legend>Select fruits (checkbox)</legend>
<label>
<input type="checkbox" name="fruits" value="orange" />
Orange
</label>
<label>
<input type="checkbox" name="fruits" value="apple" />
Apple
</label>
<label>
<input type="checkbox" name="fruits" value="banana" />
Banana
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
);
}
Now, if you want to access the field values, you typically create state and change handlers for each input, like user
, select
, fruit
, and fruits
. I know the field names are a bit simplistic, but let's focus on the core idea.
Accessing field values is still manageable, sure — but there's a cost: each state change triggers a re-render. That can add up, especially with large forms.
This is exactly where the useActionState
hook shines.
What Is the useActionState
Hook?
The useActionState
hook in React 19 simplifies form submissions by managing form state updates and server actions together.
No need for useState
or manual event handlers anymore.
It takes three parameters:
-
actionFn
: A function called when the form is submitted. It takes two arguments:
- A previous state
- Form data (type
FormData
)-
initialState
: The initial value for the form state. This is used only before the first submission. -
formRef
(optional): A ref to the form element. Helps automatically collect form data when the form is submitted.
-
It returns an array with:
-
currentState
: The latest form state returned byactionFn
-
action
: A function to assign to the form'saction
attribute (<form action={action} ...>
) -
isPending
: A boolean indicating whether the form is currently submitting (great for loading states)
Working Example: Hands-on With useActionState
Now that we've covered how useActionState
works, let's put it into practice with a simple form component — the same one we've been using earlier.
Action Function:
export type FormState = {
user: string;
fruit: string;
fruits: string[];
select: string;
};
export const submitForm = (prevState: FormState, formData: FormData): FormState => {
const user = formData.get("user")?.toString() || "";
const fruit = formData.get("fruit")?.toString() || "";
const fruits = formData.getAll("fruits").map(String);
const select = formData.get("select")?.toString() || "";
console.log({ user, fruit, fruits, select });
return {
...prevState,
user,
fruit,
fruits,
select,
};
};
Updated Form Component:
import { useActionState } from "react";
import { submitForm } from "../actions/submitForm";
import type { FormState } from "../actions/submitForm";
export default function Form() {
const [formState, formAction] = useActionState<FormState, FormData>(
submitForm,
{
user: "",
fruit: "",
fruits: [],
select: "",
}
);
return (
<div className="form-cont">
<form action={formAction}>
<input
type="text"
placeholder="user"
name="user"
defaultValue={formState.user}
/>
<select name="select" defaultValue={formState.select}>
<option disabled value="">
--
</option>
<option>Banana</option>
<option>Apple</option>
<option>Orange</option>
</select>
<div>
<label>
<input
type="radio"
name="fruit"
value="orange"
defaultChecked={formState.fruit === "orange"}
/>
Orange
</label>
<label>
<input
type="radio"
name="fruit"
value="apple"
defaultChecked={formState.fruit === "apple"}
/>
Apple
</label>
<label>
<input
type="radio"
name="fruit"
value="banana"
defaultChecked={formState.fruit === "banana"}
/>
Banana
</label>
</div>
<div>
<label>
<input
type="checkbox"
name="fruits"
value="orange"
defaultChecked={formState.fruits.includes("orange")}
/>
Orange
</label>
<label>
<input
type="checkbox"
name="fruits"
value="apple"
defaultChecked={formState.fruits.includes("apple")}
/>
Apple
</label>
<label>
<input
type="checkbox"
name="fruits"
value="banana"
defaultChecked={formState.fruits.includes("banana")}
/>
Banana
</label>
</div>
<button>Submit</button>
</form>
</div>
);
}
Here, we call the useActionState
hook with our submitForm
action and provide an initial form state. One little but important detail — we add defaultValue
and defaultChecked
to the inputs. Why? Because when you submit the form, the inputs reset. This helps retain the values after submission. If needed, you could also implement a resetValues
function.
A Fully Working Demo
About the Author
Bharat has been a Front-End developer since 2011. He has a thing for building great developer experience. He loves learning and teaching all things tech.
Top comments (0)