Iterative approach using React Router
-
we are currently using the React Router library (v6) to handle routing within our app
-
it's important to understand a couple of React Router concepts to fully understand our solution:
- React Router 6 supports nested routes
- there are 2 types of routes which React Router supports:
- standard, path routes which render a specific React component on a given path
- index routes - children routes with no path specified that render in the parents
<Outlet />
, at the parents URL
- for more detailed explanation explore the main concepts within the official documentation
-
we define route paths within an enum (useful because they can be iterated over, for some helper functions):
export enum RoutePaths {
// public paths
SIGNIN = "/signin",
SIGNUP = "/signup",
CONFIRM_SIGNUP = "/confirmation",
FORGOT_PASSWORD = "/forgot-password",
RESET_PASSWORD = "/reset-password",
// root paths
ROOT = "/",
WORKSPACE_ROOT = "/:workspaceSlug",
WORKSPACE_APP_ROOT = "/:workspaceSlug/:appKey",
// user feedback paths
USER_FEEDBACK = "/:workspaceSlug/:appKey/user-feedback",
USER_FEEDBACK_DETAILS = "/:workspaceSlug/:appKey/user-feedback/:userFeedbackKey",
// crashes paths
CRASH_REPORTS = "/:workspaceSlug/:appKey/crash-reports",
CRASH_REPORT_ROOT = "/:workspaceSlug/:appKey/crash-reports/:crashReportKey",
CRASH_REPORT_OVERVIEW = "/:workspaceSlug/:appKey/crash-reports/:crashReportKey/overview",
CRASH_REPORT_EVENTS = "/:workspaceSlug/:appKey/crash-reports/:crashReportKey/events",
CRASH_REPORT_EVENT = "/:workspaceSlug/:appKey/crash-reports/:crashReportKey/events/:crashReportEventId",
// ... other paths
// not found path
NOT_FOUND = "*",
}
- routes are defined in arrays within the configuration file, with each route potentially containing children routes
- parent routes are used for layout components, where as index routes are used for redirecting (if necessary)
// Route typings
export enum RouteConfigType {
PATH,
INDEX,
}
export interface PathRouteConfig {
name: string;
type: RouteConfigType.PATH;
path: RoutePaths;
element?: () => JSX.Element;
subRoutes?: RouteConfig[];
}
export interface IndexRouteConfig {
type: RouteConfigType.INDEX;
index: true;
element?: () => JSX.Element;
}
export type RouteConfig = PathRouteConfig | IndexRouteConfig;
// Route configurations
export const routesConfig: RouteConfig[] = [
{
name: "Root",
type: RouteConfigType.PATH,
path: RoutePaths.ROOT,
element: MainLayout,
subRoutes: rootRouteConfigs,
},
{
name: "Sign in",
type: RouteConfigType.PATH,
path: RoutePaths.SIGNIN,
element: SignInPage,
},
{
name: "Sign up",
type: RouteConfigType.PATH,
path: RoutePaths.SIGNUP,
element: SignUpPage,
},
// ...other routes
{
name: "Not found",
type: RouteConfigType.PATH,
path: RoutePaths.NOT_FOUND,
element: NotFoundPage,
},
];
export const rootRouteConfigs: RouteConfig[] = [
{
name: "Workspace root",
type: RouteConfigType.PATH,
path: RoutePaths.WORKSPACE_ROOT,
element: SidebarLayout,
subRoutes: workspaceRouteConfigs,
},
{
name: "Join workspace",
type: RouteConfigType.PATH,
path: RoutePaths.JOIN_WORKSPACE,
element: WorkspaceJoinPage,
},
{
name: "Create workspace",
type: RouteConfigType.PATH,
path: RoutePaths.CREATE_WORKSPACE,
element: WorkspaceCreatePage,
},
// ...other routes
{
type: RouteConfigType.INDEX,
index: true,
element: WorkspaceRootRedirect,
},
];
- routes, and their child routes, are iterated over using a recursive function (recursive case is when the configuration has children routes):
export default function Router() {
return (
<BrowserRouter>
<Routes>{mapConfigsToRoutes(routesConfig)}</Routes>
</BrowserRouter>
);
}
function mapConfigsToRoutes(routeConfigs: RouteConfig[]) {
return routeConfigs.map((config, key) => mapConfigToRoute(config, key));
}
function mapConfigToRoute(config: RouteConfig, key?: number) {
const Component = config.element as React.ElementType;
const element = config.element ? <Component /> : null;
switch (config.type) {
case RouteConfigType.INDEX:
return <Route key={key} index element={element} />;
case RouteConfigType.PATH:
return (
<Route key={key} path={config.path} element={element}>
{config.subRoutes && mapConfigsToRoutes(config.subRoutes)}
</Route>
);
}
}
- with this the addition of new routes is simple, and it doesn't require maintaining a huge component with all routes
- common layouts are handled with parent routes, where as redirections are done through index routes