By route management, I mean the following:
In this chapter we'll look at these three aspects in detail. In the next chapter, we'll see how components can make navigation requests that result in url changes.
The advantage of declaring routes separately is that we can use them in the router, but also in any
other function, as we shall see later. I declare my routes in a RouteTable
instance, together with a
RoutesT
type that contains the signature of each route. Here is the route table for the posts
module:
import { RouteTable } from '/src/routes/utils/RouteTable';
export const routes = {
post: () => '/posts/:postSlug',
posts: () => '/posts',
};
export type RoutesT = {
post: (args: { postSlug: string }) => void,
posts: () => void,
};
export const getRouteTable = () => {
const routeTable = new RouteTable();
routeTable.addRoutes(routes);
return routeTable;
};
// In /src/routes/routeTable.ts we add the route tables
// from each module into a single table.
import { getRouteTable as getPostsRouteTable } from '/src/posts/routeTable';
import { getRouteTable as getFramesRouteTable } from '/src/frames/routeTable';
import { RouteFnByNameT, RouteTable } from '/src/routes/utils/RouteTable';
export const createRouteTable = () => {
const routeTable = new RouteTable();
routeTable.addTable(getPostsRouteTable());
routeTable.addTable(getFramesRouteTable());
return routeTable;
};
export const routeTable = createRouteTable();
export const getRouteFns = <T>() => {
return routeTable.routeFnByName as RouteFnByNameT<T>;
};
;
import { getRouteFns } from '/src/posts/routeTable';
import type { RoutesT as PostsRoutesT } from '/src/posts/routeTable';
const routeTable = getPostsRouteTable();
const routeFns = getRouteFns<PostsRoutesT>;
// route = "/posts/:postSlug"
const route = routeFns.post();
// routeInterpolated = "/posts/foo"
const routeInterpolated = routeFns.post({ postSlug: 'foo' });
As you can see in /src/posts/RouteTable.ts
, the information about each route is specified twice.
This is unfortunate, but necessary to get type safety.
In /src/routes/routeTable.ts
we see how the route tables for all modules are combined into a single
route table. This single route table is stored in a context and provided to all components that need it.
Now that the routes are declared we can use getRouteFns
to access the so-called route
functions, as shown in example.ts
. A route function returns the route as a string. When we pass parameters
to the route function then value interpolation is performed. When we add the route to the router (we will
discuss this below) then we don't pass parameters, so that we obtain a route string that contains the
parameter-names.
When we call getRouteFns
we have specify the types of the routes that we want to use.
This isn't perfectly type safe as we need to trust that the global route-table in fact
provides these routes, but in practice this works well enough.
The example code below shows how to declare a router. The PostsSwitch
component uses the getRouteFns
function to access the routes.
import { RoutesT as PostsRoutesT } from '/src/posts/routeTable';
import { RoutesT as FramesRoutesT } from '/src/frames/routeTable';
export const PostsSwitch = () => {
const routes = getRouteFns<PostsRoutesT & FramesRoutesT>();
return (
<Route path={routes.posts()}>
{ /* Show list of posts */ }
<PostListView className="mt-12" />
{ /* Show a particular post */ }
<Route path={routes.post()}>
<PostView className="mt-16" />
</Route>
</Route>
);
};