The useMatches hook in Remix
If you use Remix, there's a hook that you can use called useMatches
, this hook is a way to access some internal data Remix has about your app, and it let's you a lot of things! It's so awesome, that you could re-implement some of the features Remix has using it!
The hook will return an array with this interface:
interface Match {
pathname: string;
params: Params<string>;
data: RouteData;
handle: any;
}
And that array will have one match for each route that matches the current path. This usually mean you'll have at least two matches, one for the root.tsx
and another for your route, if your route has nested routes then it will have more matches.
What's so special about this? You can for example use the match.data
key to access the data returned by the loaders of a route. Or use the match.handle
to get access to the handle
export of the route.
The documentation shows you how to use the handle
export to pass elements from a child route to a parent route and render a breadcrumb. To me, the most useful property is the data
.
With the match.data
you can access to dynamic data, not static data as the breadcrumb example shows. Let's see an example:
import { useMatches } from "remix";
export function useFlashMessages(): string[] {
return useMatches()
.map((match) => match.data)
.filter((data) => Boolean(data.flash))
.map((data) => data.flash);
}
With this hook, we could access the flash
property returned by any loader to get any flash message the route wants to show, then in our root.tsx
(or anywhere else actually) we could call our useFlashMessages
hook to render them.
function FlashMessages() {
let flashMessages = useFlashMessages();
return (
<div>
{flashMessages.map((message) => (
<div>{message}</div>
))}
</div>
);
}
In my Remix i18next package I use this hook to get the translations a route need.
let namespaces = useMatches()
.flatMap((match) => (match.data?.i18n ?? {}) as Record<string, Language>)
.reduce((messages, routeMessages) => ({ ...messages, ...routeMessages }), {});
This way, any loader can return a i18n
key with all the translations it loaded for that route and the user locale, in the root I can get them and pass them to i18next to use them.
You could even use it to access a route data based on their pathname. This way you don't need to use context to pass data from a parent route to a child route, and you will be able to get a child data from a parent route.
function useParentData(pathname: string): unknown {
let matches = useMatches();
let parentMatch = matches.find((match) => match.pathname === pathname);
if (!parentMatch) return null;
return parentMatch.data;
}