Skip to content

Commit 0729641

Browse files
authored
Updates for SSR + Remix integration (#9631)
1 parent 23db292 commit 0729641

File tree

2 files changed

+164
-5
lines changed

2 files changed

+164
-5
lines changed

packages/react-router-dom/__tests__/data-static-router-test.tsx

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,14 @@ describe("A <StaticRouterProvider>", () => {
410410
it("errors if required props are not passed", async () => {
411411
let routes = [
412412
{
413-
path: "the",
413+
path: "",
414414
element: <h1>👋</h1>,
415415
},
416416
];
417417
let { query } = createStaticHandler(routes);
418418

419419
let context = (await query(
420-
new Request("http://localhost/the/path?the=query#the-hash", {
420+
new Request("http://localhost/", {
421421
signal: new AbortController().signal,
422422
})
423423
)) as StaticHandlerContext;
@@ -445,6 +445,128 @@ describe("A <StaticRouterProvider>", () => {
445445
);
446446
});
447447

448+
it("handles framework agnostic static handler routes", async () => {
449+
let frameworkAgnosticRoutes = [
450+
{
451+
path: "the",
452+
hasErrorElement: true,
453+
children: [
454+
{
455+
path: "path",
456+
hasErrorElement: true,
457+
},
458+
],
459+
},
460+
];
461+
let { query } = createStaticHandler(frameworkAgnosticRoutes);
462+
463+
let context = (await query(
464+
new Request("http://localhost/the/path", {
465+
signal: new AbortController().signal,
466+
})
467+
)) as StaticHandlerContext;
468+
469+
let frameworkAwareRoutes = [
470+
{
471+
path: "the",
472+
element: <h1>Hi!</h1>,
473+
errorElement: <h1>Error!</h1>,
474+
children: [
475+
{
476+
path: "path",
477+
element: <h2>Hi again!</h2>,
478+
errorElement: <h2>Error again!</h2>,
479+
},
480+
],
481+
},
482+
];
483+
484+
// This should add route ids + hasErrorBoundary, and also update the
485+
// context.matches to include the full framework-aware routes
486+
let router = createStaticRouter(frameworkAwareRoutes, context);
487+
488+
expect(router.routes).toMatchInlineSnapshot(`
489+
Array [
490+
Object {
491+
"children": Array [
492+
Object {
493+
"children": undefined,
494+
"element": <h2>
495+
Hi again!
496+
</h2>,
497+
"errorElement": <h2>
498+
Error again!
499+
</h2>,
500+
"hasErrorBoundary": true,
501+
"id": "0-0",
502+
"path": "path",
503+
},
504+
],
505+
"element": <h1>
506+
Hi!
507+
</h1>,
508+
"errorElement": <h1>
509+
Error!
510+
</h1>,
511+
"hasErrorBoundary": true,
512+
"id": "0",
513+
"path": "the",
514+
},
515+
]
516+
`);
517+
expect(router.state.matches).toMatchInlineSnapshot(`
518+
Array [
519+
Object {
520+
"params": Object {},
521+
"pathname": "/the",
522+
"pathnameBase": "/the",
523+
"route": Object {
524+
"children": Array [
525+
Object {
526+
"children": undefined,
527+
"element": <h2>
528+
Hi again!
529+
</h2>,
530+
"errorElement": <h2>
531+
Error again!
532+
</h2>,
533+
"hasErrorBoundary": true,
534+
"id": "0-0",
535+
"path": "path",
536+
},
537+
],
538+
"element": <h1>
539+
Hi!
540+
</h1>,
541+
"errorElement": <h1>
542+
Error!
543+
</h1>,
544+
"hasErrorBoundary": true,
545+
"id": "0",
546+
"path": "the",
547+
},
548+
},
549+
Object {
550+
"params": Object {},
551+
"pathname": "/the/path",
552+
"pathnameBase": "/the/path",
553+
"route": Object {
554+
"children": undefined,
555+
"element": <h2>
556+
Hi again!
557+
</h2>,
558+
"errorElement": <h2>
559+
Error again!
560+
</h2>,
561+
"hasErrorBoundary": true,
562+
"id": "0-0",
563+
"path": "path",
564+
},
565+
},
566+
]
567+
`);
568+
});
569+
448570
describe("boundary tracking", () => {
449571
it("tracks the deepest boundary during render", async () => {
450572
let routes = [

packages/react-router-dom/server.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from "react";
22
import type {
3+
AgnosticDataRouteObject,
34
Path,
45
RevalidationState,
56
Router as RemixRouter,
@@ -13,7 +14,12 @@ import {
1314
isRouteErrorResponse,
1415
UNSAFE_convertRoutesToDataRoutes as convertRoutesToDataRoutes,
1516
} from "@remix-run/router";
16-
import type { Location, RouteObject, To } from "react-router-dom";
17+
import type {
18+
DataRouteObject,
19+
Location,
20+
RouteObject,
21+
To,
22+
} from "react-router-dom";
1723
import { Routes } from "react-router-dom";
1824
import {
1925
createPath,
@@ -22,6 +28,7 @@ import {
2228
UNSAFE_DataRouterContext as DataRouterContext,
2329
UNSAFE_DataRouterStateContext as DataRouterStateContext,
2430
UNSAFE_DataStaticRouterContext as DataStaticRouterContext,
31+
UNSAFE_enhanceManualRouteObjects as enhanceManualRouteObjects,
2532
} from "react-router-dom";
2633

2734
export interface StaticRouterProps {
@@ -198,11 +205,41 @@ function getStatelessNavigator() {
198205
};
199206
}
200207

208+
// Temporary manifest generation - we should optimize this by combining the
209+
// tree-walks between convertRoutesToDataRoutes, enhanceManualRouteObjects,
210+
// and generateManifest.
211+
// Also look into getting rid of `route as AgnosticDataRouteObject` down below?
212+
function generateManifest(
213+
routes: DataRouteObject[],
214+
manifest: Map<string, DataRouteObject> = new Map<string, DataRouteObject>()
215+
): Map<string, RouteObject> {
216+
routes.forEach((route) => {
217+
manifest.set(route.id, route);
218+
if (route.children) {
219+
generateManifest(route.children, manifest);
220+
}
221+
});
222+
return manifest;
223+
}
224+
201225
export function unstable_createStaticRouter(
202226
routes: RouteObject[],
203227
context: StaticHandlerContext
204228
): RemixRouter {
205-
let dataRoutes = convertRoutesToDataRoutes(routes);
229+
let dataRoutes = convertRoutesToDataRoutes(enhanceManualRouteObjects(routes));
230+
let manifest = generateManifest(dataRoutes);
231+
232+
// Because our context matches may be from a framework-agnostic set of
233+
// routes passed to createStaticHandler(), we update them here with our
234+
// newly created/enhanced data routes
235+
let matches = context.matches.map((match) => {
236+
let route = manifest.get(match.route.id) || match.route;
237+
return {
238+
...match,
239+
route: route as AgnosticDataRouteObject,
240+
};
241+
});
242+
206243
let msg = (method: string) =>
207244
`You cannot use router.${method}() on the server because it is a stateless environment`;
208245

@@ -214,7 +251,7 @@ export function unstable_createStaticRouter(
214251
return {
215252
historyAction: Action.Pop,
216253
location: context.location,
217-
matches: context.matches,
254+
matches,
218255
loaderData: context.loaderData,
219256
actionData: context.actionData,
220257
errors: context.errors,

0 commit comments

Comments
 (0)