# How React Router Handles Form Actions for Data Mutations

> Discover how React Router manages form actions for data mutations. Learn about its specialized pipeline for validation, action execution, and state updates.

- Repository: [Remix/react-router](https://github.com/remix-run/react-router)
- Tags: deep-dive
- Published: 2026-03-06

---

**React Router treats `<Form>` submissions with mutation methods (POST, PUT, PATCH, DELETE) as action requests, routing them through a specialized pipeline that validates CSRF protection, executes the matched route's action function, and handles redirects, errors, or success states before committing results to the router state.**

React Router's declarative API transforms standard HTML forms into powerful data mutation primitives. In the `remix-run/react-router` codebase, the router's core navigation loop distinguishes between data loading and data mutation, triggering a specialized action execution pipeline whenever a form submission uses non-GET methods.

## The Mutation Detection Pipeline

### Identifying Mutation Methods

When a navigation is triggered via a `<Form>` submission, the router immediately checks `isMutationMethod(submission.formMethod)` to determine if the request requires action handling. This validation occurs early in the navigation pipeline at [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts) (lines 1810-1815), ensuring that POST, PUT, PATCH, and DELETE requests are routed to the action execution path while GET requests proceed to standard data loading.

### Entering the Submitting State

Upon detecting a mutation, the `handleAction` function marks the router as *submitting* by calling `updateState({ navigation })` at lines 1906-1920 in [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts). This state transition optionally aborts any ongoing load operations to prevent race conditions between data fetching and mutation requests.

## Action Route Resolution and Execution

### Fog-of-War Discovery for Lazy Routes

For applications utilizing code splitting and lazy-loaded routes, `handleAction` executes `discoverRoutes` (lines 2224-2240) to load missing leaf routes before invoking the action. This ensures the router has the complete route tree available to locate the correct action handler, even for dynamically imported route modules.

### Target Route Selection

The router locates the action target using `getTargetMatch(matches, location)` at lines 7010-7020 in [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts). This function selects the deepest *path-contributing* match or an index route that will receive the form data, ensuring nested route layouts don't accidentally intercept actions intended for deeper leaf nodes.

### Action Function Invocation

Once the target route is identified, the router validates that an `action` function exists. If the route lacks an action (or lazy loader), the router returns a 405 Method Not Allowed error result (lines 7980-7990). Otherwise, it constructs a data-strategy payload via `callDataStrategy` (lines 7998-8015) that executes the user-provided `action` function and captures its `DataResult`.

## Processing Action Results

### Handling Redirects

If the action returns a redirect response, detected via `isRedirectResult`, the router immediately starts a redirect navigation and short-circuits the current flow (lines 8224-8245). This prevents unnecessary loader revalidation after a successful mutation that changes the URL.

### Error Boundary Integration

When an action throws or returns an error result (`isErrorResult`), the router records a *pending error* on the nearest error boundary and flips the navigation type to `Push` at lines 8260-8280. This allows users to navigate back and retry the failed submission, preserving the form state in the browser history stack.

### Success State and useActionData

For successful data results, the router returns `{ pendingActionResult: [routeId, result] }` (lines 8300-8310), which is later merged into the router state and exposed via the **`useActionData()`** hook. This makes action results available to components for displaying success messages or validation errors.

## Security and State Management

### CSRF Protection

Before executing any action, the router validates the request origin through `throwIfPotentialCSRFAttack` in [`packages/react-router/lib/actions.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/actions.ts) (lines 1-38). This function compares the `origin` header against `host` / `x-forwarded-host` and any configured `allowedActionOrigins`, rejecting cross-site request forgery attempts before they reach application code.

### Navigation Completion

After action execution (and subsequent loader revalidation unless short-circuited), `completeNavigation` finalizes the transition at lines 9660-9670 in [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts). This updates `state.actionData` with the action result, clears the submitting state, and triggers a UI re-render with the mutated data.

## Practical Implementation Example

```tsx
// src/routes/contact.tsx
export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  await saveMessage(formData.get("message"));
  // redirect after successful mutation
  return redirect("/thanks");
}

// In the component
export default function Contact() {
  const actionData = useActionData(); // receives the result of the action
  const transition = useTransition(); // tells you if the form is submitting

  return (
    <Form method="post" action="/contact">
      <textarea name="message" required />
      <button type="submit" disabled={transition.state === "submitting"}>
        {transition.state === "submitting" ? "Sending…" : "Send"}
      </button>
      {actionData?.error && <p className="error">{actionData.error}</p>}
    </Form>
  );
}

```

The `Form` component automatically creates a `Submission` object, the router detects the `POST` method as a mutation, runs the exported `action`, and then either redirects or provides the result via `useActionData()`.

## Summary

- React Router detects mutations via `isMutationMethod`, routing POST/PUT/PATCH/DELETE to `handleAction` instead of standard loaders
- The pipeline includes fog-of-war discovery for lazy routes and `getTargetMatch` for precise target selection
- Actions execute through `callDataStrategy`, with automatic 405 errors when routes lack action exports
- Results are categorized as redirects (immediate navigation), errors (boundary capture), or success (`useActionData`)
- CSRF protection runs automatically via `throwIfPotentialCSRFAttack` before any action code executes
- State updates finalize through `completeNavigation`, committing results to `state.actionData` and re-rendering the UI

## Frequently Asked Questions

### What HTTP methods trigger React Router form actions?

Methods defined as mutations—**POST**, **PUT**, **PATCH**, and **DELETE**—trigger the action pipeline. GET submissions bypass actions and proceed to standard data loading, as determined by the `isMutationMethod` check in the router source.

### How does React Router handle form actions on lazy-loaded routes?

The router runs `discoverRoutes` during `handleAction` (lines 2224-2240) to fetch missing route definitions before executing the action. This ensures code-split routes can handle form submissions without requiring eager loading of all route modules.

### What happens if a route doesn't export an action function?

The router returns a **405 Method Not Allowed** error result immediately (lines 7980-7990), preventing the submission from proceeding. This safety check ensures that only routes explicitly designed to handle mutations receive form data.

### How is CSRF protection implemented for React Router form actions?

The `throwIfPotentialCSRFAttack` function in [`packages/react-router/lib/actions.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/actions.ts) validates the request `origin` header against the `host` or `x-forwarded-host` headers and any configured `allowedActionOrigins`. Invalid origins are rejected before the action function executes, preventing cross-site request forgery attacks.