Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-router-dom): add createModuleRoutes utility function #9830

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions packages/react-router-dom-v5-compat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export {
UNSAFE_RouteContext,
createPath,
createRoutesFromChildren,
createModuleRoutes,
createSearchParams,
generatePath,
matchPath,
Expand Down
1 change: 1 addition & 0 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export {
createPath,
createRoutesFromChildren,
createRoutesFromElements,
createModuleRoutes,
defer,
isRouteErrorResponse,
generatePath,
Expand Down
2 changes: 2 additions & 0 deletions packages/react-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import type {
import {
enhanceManualRouteObjects,
createRoutesFromChildren,
createModuleRoutes,
renderMatches,
Await,
MemoryRouter,
Expand Down Expand Up @@ -167,6 +168,7 @@ export {
createPath,
createRoutesFromChildren,
createRoutesFromChildren as createRoutesFromElements,
createModuleRoutes,
defer,
isRouteErrorResponse,
generatePath,
Expand Down
100 changes: 100 additions & 0 deletions packages/react-router/lib/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
useNavigate,
useOutlet,
useRoutes,
useRouteError,
_renderMatches,
} from "./hooks";

Expand Down Expand Up @@ -601,6 +602,105 @@ export function createRoutesFromChildren(
return routes;
}

/**
* Converts Route objects with a `module` property that import a module that
rossipedia marked this conversation as resolved.
Show resolved Hide resolved
* conforms to the Remix route module convention to React Router's standard
* route object. Properties directly set on the route object override exports
* from the route module.
*/
export function createModuleRoutes(
routes: (ModuleRouteObject | RouteObject)[]
): RouteObject[] {
return routes.map((route) => {
if (!isModuleRouteObject(route)) {
return route;
}
const { module: moduleFactory, children, ...restOfRoute } = route;

let element;
if (!route.element) {
let Component = React.lazy(moduleFactory);
element = <Component />;
}

let loader: RouteObject['loader'] = route.loader;
if (typeof loader !== 'function') {
loader = async (args) => {
const mod = await moduleFactory();
return typeof mod.loader === 'function' ? mod.loader(args) : null;
};
}

let action: RouteObject['action'] = route.loader;
if (typeof action !== 'function') {
action = async (args) => {
const mod = await moduleFactory();
return typeof mod.action === 'function' ? mod.action(args) : null;
};
}

let errorElement = route.errorElement;
if (errorElement) {
rossipedia marked this conversation as resolved.
Show resolved Hide resolved
let ErrorBoundary = React.lazy(async function () {
const mod = await moduleFactory();
return {
default:
typeof mod.ErrorBoundary === 'function'
? mod.ErrorBoundary
: ModuleRoutePassthroughErrorBoundary,
};
});

errorElement = <ErrorBoundary />;
}

return {
...restOfRoute,
element,
loader,
action,
errorElement,
children: children ? createModuleRoutes(children) : undefined,
} as RouteObject;
});
}

function isModuleRouteObject(
route: ModuleRouteObject | RouteObject
): route is ModuleRouteObject {
return 'module' in route && typeof route.module === 'function';
}

function ModuleRoutePassthroughErrorBoundary() {
let error = useRouteError();
throw error;
// This is necessary for the
rossipedia marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line no-unreachable
return null;
}

export interface ModuleNonIndexRouteObject extends NonIndexRouteObject {
module: ModuleRouteFactory;
children: (ModuleRouteObject | RouteObject)[];
}
export interface ModuleIndexRouteObject extends IndexRouteObject {
module: ModuleRouteFactory;
children?: undefined;
}

type ModuleRouteObject = ModuleNonIndexRouteObject | ModuleIndexRouteObject;

interface ModuleRouteModule {
default: React.ComponentType<any>;
loader?: RouteObject['loader'];
action?: RouteObject['action'];
ErrorBoundary?: React.ComponentType<any>;
}

interface ModuleRouteFactory {
(): Promise<ModuleRouteModule>;
}

/**
* Renders the result of `matchRoutes()` into a React element.
*/
Expand Down