diff --git a/packages/inferno-router/__tests__/loaderOnRoute.spec.tsx b/packages/inferno-router/__tests__/loaderOnRoute.spec.tsx
index 9ac444ba7..5ce617d40 100644
--- a/packages/inferno-router/__tests__/loaderOnRoute.spec.tsx
+++ b/packages/inferno-router/__tests__/loaderOnRoute.spec.tsx
@@ -693,4 +693,44 @@ describe('Resolve loaders during server side rendering', () => {
const result = await resolveLoaders(loaderEntries);
expect(result).toEqual(initialData);
});
+
+
+ it('Can resolve with sub classed Route', async () => {
+ class MyRoute extends Route {
+ constructor(props, context) {
+ super(props, context);
+ }
+ }
+ const TEXT = 'bubblegum';
+ const Component = (props) => {
+ const res = useLoaderData(props);
+ return
{res?.message}
;
+ };
+
+ const loaderFuncNoHit = async () => {
+ return { message: 'no' };
+ };
+ const loaderFunc = async () => {
+ return { message: TEXT };
+ };
+
+ const initialData = {
+ '/flowers': { res: await loaderFunc() },
+ '/flowers/birds': { res: await loaderFunc() }
+ };
+
+ const app = (
+
+
+
+
+ {null}
+
+
+ );
+
+ const loaderEntries = traverseLoaders('/flowers/birds', app);
+ const result = await resolveLoaders(loaderEntries);
+ expect(result).toEqual(initialData);
+ });
});
diff --git a/packages/inferno-router/__tests__/loaderWithSwitch.spec.tsx b/packages/inferno-router/__tests__/loaderWithSwitch.spec.tsx
index 682b65683..c6798ba8e 100644
--- a/packages/inferno-router/__tests__/loaderWithSwitch.spec.tsx
+++ b/packages/inferno-router/__tests__/loaderWithSwitch.spec.tsx
@@ -1,12 +1,5 @@
import { render, rerender } from 'inferno';
-import {
- MemoryRouter,
- Route,
- Switch,
- NavLink,
- useLoaderData,
- useLoaderError,
-} from 'inferno-router';
+import { MemoryRouter, StaticRouter, Route, Switch, NavLink, useLoaderData, useLoaderError, resolveLoaders, traverseLoaders } from 'inferno-router';
// Cherry picked relative import so we don't get node-stuff from inferno-server in browser test
import { createEventGuard } from './testUtils';
@@ -44,7 +37,7 @@ describe('A with loader in a MemoryRouter', () => {
loader={loaderFunc}
/>
,
- container,
+ container
);
// Wait until async loader has completed
@@ -73,7 +66,7 @@ describe('A with loader in a MemoryRouter', () => {
loader={loaderFunc}
/>
,
- container,
+ container
);
// Wait until async loader has completed
@@ -92,14 +85,14 @@ describe('A with loader in a MemoryRouter', () => {
return { message: TEXT };
};
const initialData = {
- '/flowers': { res: await loaderFunc(), err: undefined },
+ '/flowers': { res: await loaderFunc(), err: undefined }
};
render(
,
- container,
+ container
);
expect(container.innerHTML).toContain(TEXT);
@@ -178,14 +171,14 @@ describe('A with loader in a MemoryRouter', () => {
return { message: TEXT };
};
const initialData = {
- '/flowers': { res: await loaderFunc(), err: undefined },
+ '/flowers': { res: await loaderFunc(), err: undefined }
};
render(
,
- container,
+ container
);
expect(container.innerHTML).toContain(TEXT);
@@ -283,7 +276,7 @@ describe('A with loader in a MemoryRouter', () => {
/>
,
- container,
+ container
);
// Check that we are starting in the right place
@@ -371,3 +364,102 @@ describe('A with loader in a MemoryRouter', () => {
expect(container.querySelector('#publish')).toBeNull();
});
});
+
+describe('Resolve loaders during server side rendering', () => {
+ it('Can resolve with single route', async () => {
+ const TEXT = 'bubblegum';
+ const Component = (props) => {
+ const res = useLoaderData(props);
+ return {res?.message}
;
+ };
+
+ const loaderFunc = async () => {
+ return { message: TEXT };
+ };
+
+ const initialData = {
+ '/flowers': { res: await loaderFunc() }
+ };
+
+ const app = (
+
+
+
+
+
+ );
+
+ const loaderEntries = traverseLoaders('/flowers', app);
+ const result = await resolveLoaders(loaderEntries);
+ expect(result).toEqual(initialData);
+ });
+
+ it('Can resolve with multiple routes', async () => {
+ const TEXT = 'bubblegum';
+ const Component = (props) => {
+ const res = useLoaderData(props);
+ return {res?.message}
;
+ };
+
+ const loaderFuncNoHit = async () => {
+ return { message: 'no' };
+ };
+ const loaderFunc = async () => {
+ return { message: TEXT };
+ };
+
+ const initialData = {
+ '/birds': { res: await loaderFunc() }
+ };
+
+ const app = (
+
+
+
+
+
+
+
+ );
+
+ const loaderEntries = traverseLoaders('/birds', app);
+ const result = await resolveLoaders(loaderEntries);
+ expect(result).toEqual(initialData);
+ });
+
+ it('Can resolve with nested routes', async () => {
+ const TEXT = 'bubblegum';
+ const Component = (props) => {
+ const res = useLoaderData(props);
+ return {res?.message}
;
+ };
+
+ const loaderFuncNoHit = async () => {
+ return { message: 'no' };
+ };
+ const loaderFunc = async () => {
+ return { message: TEXT };
+ };
+
+ const initialData = {
+ '/flowers': { res: await loaderFunc() },
+ '/flowers/birds': { res: await loaderFunc() }
+ };
+
+ const app = (
+
+
+
+
+
+ {null}
+
+
+
+ );
+
+ const loaderEntries = traverseLoaders('/flowers/birds', app);
+ const result = await resolveLoaders(loaderEntries);
+ expect(result).toEqual(initialData);
+ });
+});
\ No newline at end of file
diff --git a/packages/inferno-router/src/resolveLoaders.ts b/packages/inferno-router/src/resolveLoaders.ts
index 7747c4319..423e2d1de 100644
--- a/packages/inferno-router/src/resolveLoaders.ts
+++ b/packages/inferno-router/src/resolveLoaders.ts
@@ -1,44 +1,45 @@
import { isNullOrUndef, isUndefined } from 'inferno-shared';
import { matchPath } from './matchPath';
-import type { TLoaderData } from './Router';
+import type { TLoaderData, TLoaderProps } from './Router';
import { Switch } from './Switch';
+import { Route } from './Route';
-export async function resolveLoaders(
- loaderEntries: TLoaderEntry[],
-): Promise> {
- const promises = loaderEntries.map(
- async ({ path, params, request, loader }) => {
- return await resolveEntry(path, params, request, loader);
- },
- );
- return await Promise.all(promises).then((result) => {
+export function resolveLoaders(loaderEntries: TLoaderEntry[]): Promise> {
+ const promises = loaderEntries.map(({ path, params, request, loader }) => {
+ return resolveEntry(path, params, request, loader);
+ });
+ return Promise.all(promises).then((result) => {
return Object.fromEntries(result);
});
}
-interface TLoaderEntry {
+type TLoaderEntry = {
path: string;
params: Record;
request: Request;
controller: AbortController;
loader: (TLoaderProps) => Promise;
-}
+};
-export function traverseLoaders(
- location: string,
- tree: any,
- base?: string,
-): TLoaderEntry[] {
+export function traverseLoaders(location: string, tree: any, base?: string): TLoaderEntry[] {
return _traverseLoaders(location, tree, base, false);
}
+function _isSwitch(node: any): boolean {
+ // Using the same patterns as for _isRoute, but I don't have a test where
+ // I pass a Switch via an array, but it is better to be consistent.
+ return node?.type?.prototype instanceof Switch || node?.type === Switch;
+}
+
+function _isRoute(node: any): boolean {
+ // So the === check is needed if routes are passed in an array,
+ // the instanceof test if routes are passed as children to a Component
+ // This feels inconsistent, but at least it works.
+ return node?.type?.prototype instanceof Route || node?.type === Route;
+}
+
// Optionally pass base param during SSR to get fully qualified request URI passed to loader in request param
-function _traverseLoaders(
- location: string,
- tree: any,
- base?: string,
- parentIsSwitch = false,
-): TLoaderEntry[] {
+function _traverseLoaders(location: string, tree: any, base?: string, parentIsSwitch = false): TLoaderEntry[] {
// Make sure tree isn't null
if (isNullOrUndef(tree)) return [];
@@ -47,12 +48,7 @@ function _traverseLoaders(
const entriesOfArr = tree.reduce((res, node) => {
if (parentIsSwitch && hasMatch) return res;
- const outpArr = _traverseLoaders(
- location,
- node,
- base,
- node?.type?.prototype instanceof Switch,
- );
+ const outpArr = _traverseLoaders(location, node, base, _isSwitch(node));
if (parentIsSwitch && outpArr.length > 0) {
hasMatch = true;
}
@@ -62,63 +58,47 @@ function _traverseLoaders(
}
const outp: TLoaderEntry[] = [];
- let isRouteButNotMatch = false;
- if (tree.props) {
- // TODO: If we traverse a switch, only the first match should be returned
+ if (_isRoute(tree) && tree.props) {
// TODO: Should we check if we are in Router? It is defensive and could save a bit of time, but is it worth it?
- const {
- path,
- exact = false,
- strict = false,
- sensitive = false,
- } = tree.props;
+ const { path, exact = false, strict = false, sensitive = false } = tree.props;
const match = matchPath(location, {
exact,
path,
sensitive,
- strict,
+ strict
});
// So we can bail out of recursion it this was a Route which didn't match
- isRouteButNotMatch = !match;
-
- // Add any loader on this node (but only on the VNode)
- if (match && !tree.context && tree.props?.loader && tree.props?.path) {
+ if (!match) {
+ return outp;
+ } else if (!tree.context && tree.props?.loader && tree.props?.path) {
+ // Add any loader on this node (but only on the VNode)
const { params } = match;
const controller = new AbortController();
- const request = createClientSideRequest(
- location,
- controller.signal,
- base,
- );
+ const request = createClientSideRequest(location, controller.signal, base);
outp.push({
controller,
loader: tree.props.loader,
params,
path,
- request,
+ request
});
}
}
- // Traverse ends here
- if (isRouteButNotMatch) return outp;
-
// Traverse children
- const entries = _traverseLoaders(
- location,
- tree.children || tree.props?.children,
- base,
- tree.type?.prototype instanceof Switch,
- );
+ const children = tree.children ?? tree.props?.children;
+ if (isNullOrUndef(children)) return outp;
+
+ const entries = _traverseLoaders(location, children, base, _isSwitch(tree));
return [...outp, ...entries];
}
-async function resolveEntry(path, params, request, loader): Promise {
+function resolveEntry(path, params, request, loader): Promise {
return (
loader({ params, request })
- .then(async (res: any) => {
+ .then((res: any) => {
// This implementation is based on:
// https://github.com/remix-run/react-router/blob/4f3ad7b96e6e0228cc952cd7eafe2c265c7393c7/packages/router/router.ts#L2787-L2879
@@ -137,17 +117,19 @@ async function resolveEntry(path, params, request, loader): Promise {
dataPromise = res.text();
}
- return await dataPromise
- .then((body) => {
- // We got a JSON error
- if (!res.ok) {
- return [path, { err: body }];
- }
- // We got JSON response
- return [path, { res: body }];
- })
- // Could not parse JSON
- .catch((err) => [path, { err }]);
+ return (
+ dataPromise
+ .then((body) => {
+ // We got a JSON error
+ if (!res.ok) {
+ return [path, { err: body }];
+ }
+ // We got JSON response
+ return [path, { res: body }];
+ })
+ // Could not parse JSON
+ .catch((err) => [path, { err }])
+ );
})
// Could not fetch data
.catch((err) => [path, { err }])
@@ -158,9 +140,7 @@ async function resolveEntry(path, params, request, loader): Promise {
// NOTE: We don't currently support the submission param of createClientSideRequest which is why
// some of the related code is commented away
-export type FormEncType =
- | 'application/x-www-form-urlencoded'
- | 'multipart/form-data';
+export type FormEncType = 'application/x-www-form-urlencoded' | 'multipart/form-data';
export type MutationFormMethod = 'post' | 'put' | 'patch' | 'delete';
export type FormMethod = 'get' | MutationFormMethod;
@@ -193,12 +173,9 @@ function createClientSideRequest(
location: string | Location,
signal: AbortSignal,
// submission?: Submission
- base?: string,
+ base?: string
): Request {
- const url =
- inBrowser || !isUndefined(base)
- ? createClientSideURL(location, base)
- : location.toString();
+ const url = inBrowser || !isUndefined(base) ? createClientSideURL(location, base) : location.toString();
const init: RequestInit = { signal };
// TODO: react-router supports submitting forms with loaders, but this needs more investigation
@@ -214,7 +191,7 @@ function createClientSideRequest(
// Request is undefined when running tests
if (process.env.NODE_ENV === 'test' && typeof Request === 'undefined') {
- // @ts-expect-error global req
+ // @ts-ignore
global.Request = class Request {
public url;
public signal;
@@ -233,18 +210,12 @@ function createClientSideRequest(
* Parses a string URL path into its separate pathname, search, and hash components.
*/
-export function createClientSideURL(
- location: Location | string,
- base?: string,
-): URL {
+export function createClientSideURL(location: Location | string, base?: string): URL {
if (base === undefined && typeof window !== 'undefined') {
// window.location.origin is "null" (the literal string value) in Firefox
// under certain conditions, notably when serving from a local HTML file
// See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
- base =
- window?.location?.origin !== 'null'
- ? window.location.origin
- : window.location.href;
+ base = window?.location?.origin !== 'null' ? window.location.origin : window.location.href;
}
const url = new URL(location.toString(), base);