action
On this page

action

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 UX capabilities of modern SPAs.

This feature only works if using a data router like createBrowserRouter

<Route
  path="/song/:songId/edit"
  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",
});

params

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

<Route
  path="/projects/:projectId/delete"
  action={({ params }) => {
    return fakeDeleteProject(params.projectId);
  }}
/>

request

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

<Route
  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?

<form
  onSubmit={(event) => {
    event.preventDefault();
    // ...
  }}
/>

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>
</Form>;

// accessed by the same names
formData.get("songTitle");
formData.get("lyrics");

For more information on formData see Working with FormData.

Opt-in serialization types

Note that when using useSubmit you may also pass encType: "application/json" or encType: "text/plain" to instead serialize your payload into request.json() or request.text().

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".

<Route
  action={async ({ params, request }) => {
    const res = await fetch(
      `/api/properties/${params.id}`,
      {
        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.

Handling multiple actions per route

A fairly common question that pops up is "What if I need to handle multiple different behaviors in my action?" There's a few ways to accomplish this, but usually the simplest is to put a name/value on your <button type="submit"> and use that in the action to decide which code to execute (that's right - submitting buttons can have name/value attributes!):

async function action({ request }) {
  let formData = await request.formData();
  let intent = formData.get("intent");

  if (intent === "edit") {
    await editSong(formData);
    return { ok: true };
  }

  if (intent === "add") {
    await addSong(formData);
    return { ok: true };
  }

  throw json(
    { message: "Invalid intent" },
    { status: 400 }
  );
}

function Component() {
  let song = useLoaderData();

  // When the song exists, show an edit form
  if (song) {
    return (
      <Form method="post">
        <p>Edit song lyrics:</p>
        {/* Edit song inputs */}
        <button type="submit" name="intent" value="edit">
          Edit
        </button>
      </Form>
    );
  }

  // Otherwise show a form to add a new song
  return (
    <Form method="post">
      <p>Add new lyrics:</p>
      {/* Add song inputs */}
      <button type="submit" name="intent" value="add">
        Add
      </button>
    </Form>
  );
}

If a button name/value isn't right for your use case, you could also use a hidden input to send and intent or you could submit different HTTP methods via the <Form method> prop (POST for add, PUT/PATCH for edit, DELETE for remove).

Docs and examples CC 4.0