On this page


Route actions are the "writes" to route loader "reads". They provide a way for apps to perform data mutations with simple HTML and HTTP semantics while React Router abstracts away the complexity of asynchronous UI and revalidation. This gives you the simple mental model of HTML + HTTP (where the browser handles the asynchrony and revalidation) with the behavior and and UX capabilities of modern SPAs.

This feature only works if using a data router

  element={<EditSong />}
  action={async ({ params, request }) => {
    let formData = await request.formData();
    return fakeUpdateSong(params.songId, formData);
  loader={({ params }) => {
    return fakeGetSong(params.songId);

Actions are called whenever the app sends a non-get submission ("post", "put", "patch", "delete") to your route. This can happen in a few ways:

// forms
<Form method="post" action="/songs" />;
<fetcher.Form method="put" action="/songs/123/edit" />;

// imperative submissions
let submit = useSubmit();
submit(data, {
  method: "delete",
  action: "/songs/123",
fetcher.submit(data, {
  method: "patch",
  action: "/songs/123/edit",


Route params are parsed from dynamic segments and passed to your action. This is useful for figuring out which resource to mutate:

  action={({ params }) => {
    return fakeDeleteProject(params.teamId);


This is a Fetch Request instance being sent to your route. The most common use case is to parse the FormData from the request

  action={async ({ request }) => {
    let formData = await request.formData();
    // ...

A Request?!

It might seem odd at first that actions receive a "request". Have you ever written this line of code?

  onSubmit={(event) => {
    // ...

What exactly are you preventing?

Without JavaScript, just plain HTML and an HTTP web server, that default event that was prevented is actually pretty great. Browsers will serialize all the data in the form into FormData and send it as the body of a new request to your server. Like the code above, React Router <Form> prevents the browser from sending that request and instead sends the request to your route action! This enables highly dynamic web apps with the simple model of HTML and HTTP.

Remember that the values in the formData are automatically serialized from the form submission, so your inputs need a name.

<Form method="post">
  <input name="songTitle" />
  <textarea name="lyrics" />
  <button type="submit">Save</button>

// accessed by the same names

For more information on formData see Working with FormData.

Returning Responses

While you can return anything you want from an action and get access to it from useActionData, you can also return a web Response.

For more information, see the loader documentation.

Throwing in Actions

You can throw in your action to break out of the current call stack (stop running the current code) and React Router will start over down the "error path".

  action={async ({ params }) => {
    const res = await fetch(
        method: "put",
        body: await request.formData(),
    if (!res.ok) throw res;
    return { ok: true };

For more details and expanded use cases, read the errorElement documentation.

Docs and examples CC 4.0