Route loaders are route-level functions that can specify arbitrary data requirements for that route. They can be used to:
Routes requiring data are nothing new, but the way React Location orchestrates these requirements is where the magic happens. In a traditional React application, usually, the route is rendered immediately, and the data is fetched asynchronously either via a custom hook or a suspense boundary that is hit. This is a great way to get data from a server, but it also means that the route is rendered before the data is available. It introduces the need for a lot of boilerplate code to handle the asynchronous data fetching and even worse, spinners everywhere. This is usually a sub-optimal user experience, and with route loaders, it's one that we can avoid!
Route loaders are called when:
Route loaders are extremely agnostic as to how you fetch your data. You can use any data fetching means that you like! Here are some of our favorites:
All you have to do is return a promise that resolves an object with the data you want to be available at the route.
Why do route loaders need to return an object? Because multiple route loaders can be matched at once. For example:
teamsroute might have a route loader that fetches a list of teams
teams/:teamIdroute might have another loader that fetches the individual team details.
teams route, you would return an object with the
teams key, and at the
teams/:teamId route, you would return an object with the
Each of these loader objects will be merged together into a single object that can be consumed in your routes or sub-loaders:
Route loaders are parallelized by default. This means that when a route is matched, each of the loaders it matches will be executed at the same time. This is great for performance, but it also means that if one of the loaders fails, the others will still be executed. It also means that if one of your loaders depends on the data of a parent, it will need to de-opt and await the promise of its parent before proceeding.
Here is an example of a route loader that depends on the data of a parent:
Loader data is made available to elements via the
useMatch hook. Calling
useMatch in an element will return the closest match to the component you call it from:
The built-in caching mechanisms for React Location are extremely basic on purpose since caching is not the core responsibility or purpose of React Location. That said, it ensures a very good UX out of the box by doing the bare minimum to retain navigational consistency.
By default, route loaders are called for new or changed routes in the route hierarchy that resulted from a navigation.
Given the following navigational hierarchy, the bolded routes will have their route loaders called:
As you might have noticed, only the new or changed route loaders were called during navigation and not the old ones. This is because route loader results are cached if they do not change from navigation to navigation.
Even as the session navigated back up to
/dashboard, each of the parent route loaders were not called again, but cached and reused.
maxAge of a route loader represents the amount of time in milliseconds to cache the result of the route loader. After this duration, the route loader will be called again to retrieve a fresh result. The
maxAge of a route loader can be configured in the following ways, each one overriding the next:
maxAgeevent from the loader using the loader dispatcher
useLoadRoute() when prefetching
loaderMaxAgeoption to the route definition itself
The loader dispatcher can be used to imperatively update specific aspects of a router loader's state either during or after the loader has run. This can be useful for:
maxAgefor the route loader based on the loader response
The dispatcher can be accessed in the second options bag argument of the loader function:
The dispatcher takes an event object as its only argument. The event object must have a
type property and any additional properties that correspond to that event. Here is the event type:
maxAgeevent can be used to set the maxAge of a route loader based on data from inside the loader function, like a response from a server!
loadingevent can be used to indicate that the route loader is in a loading state.
resolveevent can be used to indicate that the route loader has completed successfully and is required to pass the new loader data.
rejectevent can be used to indicate that the route loader has failed and is required to pass the error that caused the failure.
Here's an example of using a
max-age header to set the
maxAge for the loader:
Errors caught from the loader function promise are stored in the match state for you to handle in your
errorElement route element.
To access the error, you can use the
error property on the match state:
Route loaders and async elements both share a set of features to show pending states. Because they are shared, we have a dedicated Pending States section where you can learn more about them.