loader
On this page

loader

Each route can define a "loader" function to provide data to the route element before it renders.

This feature only works if using a data router, see Picking a Router

createBrowserRouter([
  {
    element: <Teams />,
    path: "teams",
    loader: async () => {
      return fakeDb.from("teams").select("*");
    },
    children: [
      {
        element: <Team />,
        path: ":teamId",
        loader: async ({ params }) => {
          return fetch(`/api/teams/${params.teamId}.json`);
        },
      },
    ],
  },
]);

As the user navigates around the app, the loaders for the next matching branch of routes will be called in parallel and their data made available to components through useLoaderData.

params

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

createBrowserRouter([
  {
    path: "/teams/:teamId",
    loader: ({ params }) => {
      return fakeGetTeam(params.teamId);
    },
  },
]);

Note that the :teamId in the path is parsed and provided as params.teamId by the same name.

request

This is a Fetch Request instance being made to your application.

function loader({ request }) {}

A request?!

It might seem odd at first that loaders receive a "request". Consider that <Link> does something like the following code and ask yourself, "what default behavior is being prevented here?".

<a
  href={props.to}
  onClick={(event) => {
    event.preventDefault();
    navigate(props.to);
  }}
/>

Without React Router, the browser would have made a Request to your server, but React Router prevented it! Instead of the browser sending the request to your server, React Router sends the request to your loaders.

The most common use case is creating a URL and reading the URLSearchParams from it:

function loader({ request }) {
  const url = new URL(request.url);
  const searchTerm = url.searchParams.get("q");
  return searchProducts(searchTerm);
}

Note that the APIs here are not React Router specific, but rather standard web objects: Request, URL, URLSearchParams.

loader.hydrate

If you are Server-Side Rendering and leveraging the future.v7_partialHydration flag for Partial Hydration, then you may wish to opt-into running a route loader on initial hydration even though it has hydration data (for example, to let a user prime a cache with the hydration data). To force a loader to run on hydration in a partial hydration scenario, you can set a hydrate property on the loader function:

Returning Responses

While you can return anything you want from a loader and get access to it from useLoaderData, you can also return a web Response.

This might not seem immediately useful, but consider fetch. Since the return value of fetch is a Response, and loaders understand responses, many loaders can return a simple fetch!

// an HTTP/REST API
function loader({ request }) {
  return fetch("/api/teams.json", {
    signal: request.signal,
  });
}

// or even a graphql endpoint
function loader({ request, params }) {
  return fetch("/_gql", {
    signal: request.signal,
    method: "post",
    body: JSON.stringify({
      query: gql`...`,
      params: params,
    }),
  });
}

You can construct the response yourself as well:

function loader({ request, params }) {
  const data = { some: "thing" };
  return new Response(JSON.stringify(data), {
    status: 200,
    headers: {
      "Content-Type": "application/json; utf-8",
    },
  });
}

React Router will automatically call response.json() so your components don't need to parse it while rendering:

function SomeRoute() {
  const data = useLoaderData();
  // { some: "thing" }
}

Using the json utility simplifies this so you don't have to construct them yourself. This next example is effectively the same as the previous example:

import { json } from "react-router-dom";

function loader({ request, params }) {
  const data = { some: "thing" };
  return json(data, { status: 200 });
}

If you're planning an upgrade to Remix, returning responses from every loader will make the migration smoother.

Throwing in Loaders

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

function loader({ request, params }) {
  const res = await fetch(`/api/properties/${params.id}`);
  if (res.status === 404) {
    throw new Response("Not Found", { status: 404 });
  }
  return res.json();
}

For more details, read the errorElement documentation.

Docs and examples CC 4.0