Handling Forms via Form ActionsEntry 5 of 10
Async Actions and Pending State
An action can be async. While it runs, isPending (from useActionState) is true.
react.devuseActionState
Notes
- Mark the action
asyncandawaityour API call inside it. isPendingturnstrueimmediately on submit andfalsewhen the action's promise settles and the new state is rendered.- Use it to disable the submit button and change its label, preventing double submits.
- Keep API calls in a separate module (e.g.
services/feedback.ts) and call them from the action.
What / Why / When
- What
Built-in loading state for actions.
- Why
No manual
isLoadingstate, notry/finallyto reset it.- When
Any action that awaits a network request.
- Use case
A feedback form that shows "Submitting…" and a thank-you message.
Code compare
// services/feedback.tsexport async function sendFeedback(comment: string) { const response = await fetch(`${import.meta.env.VITE_API_URL}/feedback`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ comment }), }); if (!response.ok) throw new Error("Could not send feedback.");}// feedback.service.ts@Injectable({ providedIn: "root" })export class FeedbackService { private http = inject(HttpClient); send(comment: string) { return this.http.post(`${environment.apiUrl}/feedback`, { comment }); }}Differences and gotchas
- The
"use client"line in your example is only needed in Next.js or other Server Components setups; it has no effect in a Vite app. - Catch expected API errors inside the action and return them as state. An uncaught throw goes to the error boundary.
- Angular's
isPendingneedsfinalize()so it resets on both success and error.
Quick reference
| React 19 | Angular |
|---|---|
async action | Service Observable + subscribe() |
isPending | isPending signal + finalize() |
Return { error } | error signal |
Return { success } | submitted signal |