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.