loader
Each route can define a "loader" function to provide data to the route element before it renders.
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 as 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.
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 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.
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.