useFormStatus is a React 19 hook from react-dom that provides status information about the last form submission. It tells you whether a form is currently submitting, what data is being sent, and more — without needing to manage loading state manually.
Important constraint: useFormStatus must be called from a component rendered inside a <form>. It reads the status of the nearest parent form.
Complete code example:
1import { useFormStatus } from "react-dom";23function SubmitButton() {4 const { pending, data, method, action } = useFormStatus();56 return (7 <button type="submit" disabled={pending}>8 {pending ? (9 <span className="flex items-center gap-2">10 <span className="spinner" /> Submitting...11 </span>12 ) : (13 "Submit"14 )}15 </button>16 );17}1819// Must be inside a <form>20function ContactForm() {21 const [state, formAction] = useActionState(submitContact, null);2223 return (24 <form action={formAction}>25 <input name="name" placeholder="Your name" required />26 <input name="email" type="email" placeholder="Email" required />27 <textarea name="message" placeholder="Message" required />28 <SubmitButton />29 {state?.error && <p className="error">{state.error}</p>}30 </form>31 );32}
Returned properties:
pending — true while the form action is executingdata — the FormData being submitted (or null if not submitting)method — "get" or "post" (the form's method)action — reference to the action function passed to the formReal-world example — optimistic updates with pending state:
1function TodoForm() {2 const [todos, setTodos] = useState<Todo[]>([]);3 const { pending } = useFormStatus();45 const addTodo = async (formData: FormData) => {6 const title = formData.get("title") as string;78 // Optimistic update — show immediately9 const optimisticTodo = {10 id: "temp-" + Date.now(),11 title,12 done: false,13 pending: true,14 };15 setTodos(prev => [...prev, optimisticTodo]);1617 // Server action18 const saved = await saveTodo(title);1920 // Replace optimistic with real data21 setTodos(prev => prev.map(t =>22 t.id === optimisticTodo.id ? { ...saved, pending: false } : t23 ));24 };2526 return (27 <div>28 <form action={addTodo}>29 <input name="title" disabled={pending} placeholder="New todo..." />30 <button disabled={pending}>Add</button>31 </form>32 <ul>33 {todos.map(t => (34 <li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>35 {t.title}36 </li>37 ))}38 </ul>39 </div>40 );41}
Key points:
<form> components<form>useOptimistic for instant UI feedback