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.

Notes

  • Mark the action async and await your API call inside it.
  • isPending turns true immediately on submit and false when 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 isLoading state, no try/finally to 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

React
// 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.");}
Angular
// 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 isPending needs finalize() so it resets on both success and error.

Quick reference

React 19Angular
async actionService Observable + subscribe()
isPendingisPending signal + finalize()
Return { error }error signal
Return { success }submitted signal