useActionState (previously called useFormState) is a React 19 hook that manages form state and handles async form actions with built-in pending state. It simplifies form handling significantly compared to manual useState + onSubmit patterns.
How it works:
(prevState, formData) and returns the new stateuseActionState(action, initialState) returns [state, formAction, isPending]formAction to a <form action={formAction}>Complete code example:
1import { useActionState } from "react";23interface FormState {4 error: string | null;5 success: boolean;6}78async function createTodo(9 prevState: FormState,10 formData: FormData11): Promise<FormState> {12 const title = formData.get("title") as string;13 const priority = formData.get("priority") as string;1415 if (!title || title.trim().length === 0) {16 return { error: "Title is required", success: false };17 }1819 try {20 await addTodo({ title: title.trim(), priority });21 return { error: null, success: true };22 } catch (err) {23 return { error: "Failed to create todo", success: false };24 }25}2627function TodoForm() {28 const [state, formAction, isPending] = useActionState(createTodo, {29 error: null,30 success: false,31 });3233 return (34 <form action={formAction}>35 <div>36 <label htmlFor="title">Title</label>37 <input id="title" name="title" type="text" disabled={isPending} />38 </div>39 <div>40 <label htmlFor="priority">Priority</label>41 <select id="priority" name="priority" disabled={isPending}>42 <option value="low">Low</option>43 <option value="medium">Medium</option>44 <option value="high">High</option>45 </select>46 </div>47 {state.error && <p className="error">{state.error}</p>}48 {state.success && <p className="success">Todo created!</p>}49 <button type="submit" disabled={isPending}>50 {isPending ? "Creating..." : "Add Todo"}51 </button>52 </form>53 );54}
Parameters:
action — async function: (prevState, formData) => newStateinitialState — the initial value of the statepermalink? — optional URL for progressive enhancement (enables server-side form processing)Returns:
state — the current form state (updated after each action completes)formAction — pass this to <form action={formAction}>isPending — true while the action is executingBenefits over manual useState:
isLoading stateWith useFormStatus for nested buttons:
1import { useFormStatus } from "react-dom";23function SubmitButton() {4 const { pending } = useFormStatus();5 return (6 <button type="submit" disabled={pending}>7 {pending ? "Saving..." : "Save"}8 </button>9 );10}
When to use: