Nested routes are a neat concept of Remix framework. The idea is that segments of the URL path of the applications define the layouts and the data dependency of those layouts. In many use cases, your view could be a composition of multiple route levels, and you may end up with the need to access the data that was fetched in one of your parent (or child) routes.
I had exactly that use case in a project I’m working on currently, and while the Remix docs provided the answer, it wasn’t well explained.
Using loader data
You can access loader data from any parent actions with the useMatches()
hook provided by Remix.
The hook simply returns the list of matched routes and data
properties corresponding to those routes. The data is, of course, the exact data produced by any of the loaders in the routes stack.
So to get the data, you need to “pick” the specific route from the list. I was exploring how to enhance the data with a little bit of the type safety, and I’ve come up with the following hook that would d also use Typescript generics to allow typing of the returned data structure.
The hook:
import { useMatches } from "@remix-run/react";
export const useRouteData = <T>(routeId: string): T | undefined => {
const matches = useMatches();
const data = matches.find((match) => match.id === routeId)?.data;
return data as T | undefined;
};
Note. The IDs of remix routes follow specific patterns. The top-level route is always "root"
, and then child routes follow the naming convention of the routes/routeName
format.
Consider the following route structure in your Remix application:
app
├── root.tsx
└── routes
├── sales
│ ├── transactions.tsx # -> 'routes/sales/transactions
└── sales.tsx # -> 'routes/sales
To access the data from the /sales
route in /sales/transactions
, you have to call the hook passing the given route ID (as string).
const data = useRouteData<DataType>("routes/sales");
How to access parent route data in Remix
The example below summarizes what the whole approach could look like:
// in app/routes/sales.tsx
import { Link, Outlet, useLoaderData } from "@remix-run/react";
import { json } from "@remix-run/node";
type SalesData = {
accounts: number;
revenue: number;
};
export const loader = () => {
return json<SalesData>({
accounts: 5,
revenue: 200,
});
};
export default function SalesPage() {
const data = useLoaderData();
return (
<div>
<h1>Sales report</h1>
<p>
We have have {data.accounts} accounts with ${data.revenue}€ revenue!
</p>
<p>
<Link to="transactions">Show transactions</Link>
</p>
<div>
<Outlet />
</div>
</div>
);
}
// in app/routes/sales/transactions.tsx
import { useRouteData } from "~/hooks/useRouteData";
type SalesData = {
accounts: number;
revenue: number;
};
export default function SalesTransactionPage() {
const routeId = "routes/sales";
// We accessing the data of the parent "/sales" route
// instructing Typescript about the type of the returned data
const data = useRouteData<SalesData>(routeId);
const isLowRevenue = data && data.revenue < 500;
return (
<div>
<p>Transactions report goes here...</p>
{isLowRevenue && <p style={{ color: "#f00" }}>Warning! Low revenue!</p>}
</div>
);
}
Some remarks
There are a couple of observations that arrived up when working on this topic:
- As the route may not be found in the stack, the generic function return is
T | undefined
, so you need to guard against possibleundefined
values! - The hook actually doesn’t care if you’re accessing the data of a parent inside the child route or a child in the parent. You can access it as long as the data was fetched and belongs to the nested routes stack! This may come in handy where your parent view/UI could depend on some data fetched inside child loaders.
- But beware: while the pattern presented in this hook may be helpful in some cases, it should also trigger you to think about how you structure your applications. Don’t create a tight coupling between the views. Data fetching should be primarily done via Remix
loader
functions. - You can access the example application: https://github.com/johny/remix-route-hooks-example
Remix Utils
Last but not least - I wasn’t the first to discover this approach :-)
You can get useRouteData() hook and many other useful utilities from wonderful remix-utils
package by Sergio Xalambrí!