Skip to content
On this page

Nested and Similar Routes

Applications frequently have nested routing structures. These nested routes have the same base path and parameters as their shared parent route and having a way to define these routes without repeating yourself is important. Take for instance an application with these routes:

  • /
  • /about
  • /users/{userId}
  • /users/{userId}/settings
  • /users/{userId}/activity

A poor implementation of this in Type Route might look something like this:

Bad Example
tsx
import { createRouter, defineRoute, param } from "type-route";

const { routes } = createRouter({
  home: defineRoute("/"),
  about: defineRoute("/about"),
  user: defineRoute(
    {
      userId: param.path.string,
    },
    (p) => `/users/${p.userId}`
  ),
  userSettings: defineRoute(
    {
      userId: param.path.string,
    },
    (p) => `/users/${p.userId}/settings`
  ),
  userActivity: defineRoute(
    {
      userId: param.path.string,
    },
    (p) => `/users/${p.userId}/activity`
  ),
});

While this would work it is not ideal and the problem only gets worse as the application gets bigger. Fortunately, the defineRoute function returns a RouteDefinition object which has an extend function to make this process easier.

Good Example
tsx
import { createRouter, defineRoute, param } from "type-route";

const user = defineRoute(
  {
    userId: param.path.string,
  },
  (p) => `/users/${p.userId}`
);

const { routes } = createRouter({
  home: defineRoute("/"),
  about: defineRoute("/about"),
  user,
  userSettings: user.extend("/settings"),
  userActivity: user.extend("/activity"),
});

The process, however, of rendering the active route could still be a little verbose.

Bad Example
tsx
import React from "react";
import { routes } from "./router.ts";
import { Route } from "type-route";

type PageProps = {
  route: Route<typeof routes>;
};

function Page(props: UserProps) {
  const { route } = props;

  if (route.name === "home") {
    return <div>Home</div>;
  }

  if (route.name === "about") {
    return <div>About</div>;
  }

  if (
    route.name === "user" ||
    route.name === "userSettings" ||
    route.name === "userActivity"
  ) {
    return <UserPage route={route} />;
  }

  return <div>Not Found</div>;
}

type UserPageProps = {
  route: Route<
    typeof routes.user | typeof routes.userSettings | typeof routes.userActivity
  >;
};

function UserPage(props: UserPageProps) {
  const { route } = props;

  let pageContents;

  if (route.name === "user") {
    pageContents = <div>Main</div>;
  } else if (route.name === "userSettings") {
    pageContents = <div>Settings</div>;
  } else if (route.name === "userActivity") {
    pageContents = <div>Activity</div>;
  }

  return (
    <>
      <div>User Id: {route.userId}</div>
      {pageContents}
    </>
  );
}

To help with this case Type Route has a createGroup function. Here's a full example using this function:

Good Example
tsx
import React from "react";
import {
  Route,
  defineRoute,
  createRouter,
  param,
  createGroup,
} from "type-route";

const user = defineRoute(
  {
    userId: param.path.string,
  },
  (p) => `/users/${p.userId}`
);

const { routes } = createRouter({
  home: defineRoute("/"),
  about: defineRoute("/about"),
  user,
  userSettings: user.extend("/settings"),
  userActivity: user.extend("/activity"),
});

const groups = {
  user: createGroup([routes.user, routes.userSettings, routes.userActivity]),
};

type PageProps = {
  route: Route<typeof routes>;
};

function Page(props: PageProps) {
  const { route } = props;

  if (route.name === "home") {
    return <div>Home</div>;
  }

  if (route.name === "about") {
    return <div>About</div>;
  }

  if (groups.user.has(route)) {
    return <UserPage route={route} />;
  }

  return <div>Not Found</div>;
}

type UserPageProps = {
  route: Route<typeof groups.user>;
};

function UserPage(props: UserPageProps) {
  const { route } = props;

  let pageContents;

  if (route.name === "user") {
    pageContents = <div>Main</div>;
  } else if (route.name === "userSettings") {
    pageContents = <div>Settings</div>;
  } else if (route.name === "userActivity") {
    pageContents = <div>Activity</div>;
  }

  return (
    <>
      <div>User Id: {route.userId}</div>
      {pageContents}
    </>
  );
}

Type Route is a Zilch project