Render Routes and Wrappers Recursively

Make app structure, render component and wrappers with props

Test project created with Create React App + typescript

React router 6 already has a routing setting in the form of an object

React-router 5 with Routes Object

This idea is not new. It’s based on a popular recursive method solution that uses a wrapper that wraps the component before rendering. The problem is that real-world applications can have very large chains of checks. For example for one page it could be – check authorization – check if account is blocked – check tariff plan – check access to page – lajout page with content width property. For each page width can be different. Now to implement this you need to write this tree manually – it becomes difficult to search for the target page component, or create compositions with the right properties manually

Example

{
  component: () => (
    <FirstCheck>
      <SecondCheck>
        <ThridCheck>
          <InfinityCheck>
            <TargetPage />
          </InfinityCheck>
        </ThridCheck>
      </SecondCheck>
    </FirstCheck>
  );
}

// There is error with props
// method put props from Route render prop to FirstCheck, but need to TargetPage
// So you must make Component that contains all checkers, but what if you want pass some props to chekers?

Main idea

Separate component and wrappers so that you can assemble wrappers on the go, any number of chains, with the right properties.

Route object shape

[
  {
    path: "/auth",
    wrappers: [{ wrapper: Auth }],
    routes: [
      {
        exact: true,
        path: "/auth",
        component: () => <Redirect to="/auth/login" />,
      },
      {
        exact: true,
        path: "/auth/login",
        component: Login,
        wrappers: [
          {
            wrapper: LayoutWithProps,
            props: { desktopWidth: "big" },
            wrrappers: [{ wrapper: AnotherChecker }],
          },
        ],
      },
      {
        exact: true,
        path: "/auth/register",
        component: Register,
        wrappers: [
          {
            wrapper: LayoutWithProps,
            props: { desktopWidth: "small" },
            wrrappers: [{ wrapper: AnotherChecker }],
          },
        ],
      },
    ],
  },
];
RoutesArray example

const routesArray: Routes[] = [
  {
    path: "/auth",
    wrappers: withAuth(),
    routes: [
      {
        exact: true,
        path: "/auth",
        component: () => <Redirect to="/auth/login" />,
      },
      {
        exact: true,
        path: "/auth/login",
        component: Login,
        wrappers: withLayout("CustomLoginWidth"),
      },
      {
        exact: true,
        path: "/auth/register",
        component: Register,
        wrappers: withLayout("CustomRegisterWidth"),
      },
    ],
  },
  {
    exact: true,
    path: "/login",
    component: Login,
    wrappers: withAuth(withLayout("AnotherWidth")),
  },
  {
    exact: true,
    path: "/register",
    component: Register,
    wrappers: withLayout("SpecificWidth", withAuth()),
  },
];

Render routes

When you prepare your structure. Just call renderRoutes = (routes: Routes[]): JSX.Element to give Switch Route structure

import renderRoutes from "./renderRoutes";

const AppRoutes = (): JSX.Element => {
    return renderRoutes(routesArray);
};

Wrappers

Wrappers are recursively nested structures. For more convenience, you can create functions that prepare these structures and take the properties you need

// Prepare auth wrapper function
import { Routes, Wrapper } from "./renderRoutes";

const withAuth = (wrappers?: Wrapper[]): Wrapper[] => [
  {
    wrapper: CheckAuth,
    wrappers,
  },
];

// Prepare layout wrapper function 
// props takes string key with string | boolean | number 
// It's no solution to pass recursive Generic type
const withLayout = (desktopWidth: string, wrappers?: Wrapper[]): Wrapper[] => [
  {
    wrapper: LayoutWithProps,
    props: { desktopWidth },
    wrappers,
  },
];

// Use in the order you want - left to right
const routes: Routes[] = [
  {
    exact: true,
    path: "/login",
    component: Login,
    wrappers: withAuth(withLayout("AnotherWidth")),
  },
];

Finally

If you have any suggestion or ideas make pull request.

I hope you will find it useful.
Thanks for reading.