Not a Remix route, but stunning view on a road somewhere in Iceland

How to access Remix route data in other routes

Not a Remix route, but stunning view on a road somewhere in Iceland
April 16, 2022development4 min read

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 possible undefined 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í!

You may also like the following content