Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 124 additions & 2 deletions packages/react-router-dom/__tests__/data-static-router-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,14 +410,14 @@ describe("A <StaticRouterProvider>", () => {
it("errors if required props are not passed", async () => {
let routes = [
{
path: "the",
path: "",
element: <h1>👋</h1>,
},
];
let { query } = createStaticHandler(routes);

let context = (await query(
new Request("http://localhost/the/path?the=query#the-hash", {
new Request("http://localhost/", {
signal: new AbortController().signal,
})
)) as StaticHandlerContext;
Expand Down Expand Up @@ -445,6 +445,128 @@ describe("A <StaticRouterProvider>", () => {
);
});

it("handles framework agnostic static handler routes", async () => {
let frameworkAgnosticRoutes = [
{
path: "the",
hasErrorElement: true,
children: [
{
path: "path",
hasErrorElement: true,
},
],
},
];
let { query } = createStaticHandler(frameworkAgnosticRoutes);

let context = (await query(
new Request("http://localhost/the/path", {
signal: new AbortController().signal,
})
)) as StaticHandlerContext;

let frameworkAwareRoutes = [
{
path: "the",
element: <h1>Hi!</h1>,
errorElement: <h1>Error!</h1>,
children: [
{
path: "path",
element: <h2>Hi again!</h2>,
errorElement: <h2>Error again!</h2>,
},
],
},
];

// This should add route ids + hasErrorBoundary, and also update the
// context.matches to include the full framework-aware routes
let router = createStaticRouter(frameworkAwareRoutes, context);

expect(router.routes).toMatchInlineSnapshot(`
Array [
Object {
"children": Array [
Object {
"children": undefined,
"element": <h2>
Hi again!
</h2>,
"errorElement": <h2>
Error again!
</h2>,
"hasErrorBoundary": true,
"id": "0-0",
"path": "path",
},
],
"element": <h1>
Hi!
</h1>,
"errorElement": <h1>
Error!
</h1>,
"hasErrorBoundary": true,
"id": "0",
"path": "the",
},
]
`);
expect(router.state.matches).toMatchInlineSnapshot(`
Array [
Object {
"params": Object {},
"pathname": "/the",
"pathnameBase": "/the",
"route": Object {
"children": Array [
Object {
"children": undefined,
"element": <h2>
Hi again!
</h2>,
"errorElement": <h2>
Error again!
</h2>,
"hasErrorBoundary": true,
"id": "0-0",
"path": "path",
},
],
"element": <h1>
Hi!
</h1>,
"errorElement": <h1>
Error!
</h1>,
"hasErrorBoundary": true,
"id": "0",
"path": "the",
},
},
Object {
"params": Object {},
"pathname": "/the/path",
"pathnameBase": "/the/path",
"route": Object {
"children": undefined,
"element": <h2>
Hi again!
</h2>,
"errorElement": <h2>
Error again!
</h2>,
"hasErrorBoundary": true,
"id": "0-0",
"path": "path",
},
},
]
`);
});

describe("boundary tracking", () => {
it("tracks the deepest boundary during render", async () => {
let routes = [
Expand Down
43 changes: 40 additions & 3 deletions packages/react-router-dom/server.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from "react";
import type {
AgnosticDataRouteObject,
Path,
RevalidationState,
Router as RemixRouter,
Expand All @@ -13,7 +14,12 @@ import {
isRouteErrorResponse,
UNSAFE_convertRoutesToDataRoutes as convertRoutesToDataRoutes,
} from "@remix-run/router";
import type { Location, RouteObject, To } from "react-router-dom";
import type {
DataRouteObject,
Location,
RouteObject,
To,
} from "react-router-dom";
import { Routes } from "react-router-dom";
import {
createPath,
Expand All @@ -22,6 +28,7 @@ import {
UNSAFE_DataRouterContext as DataRouterContext,
UNSAFE_DataRouterStateContext as DataRouterStateContext,
UNSAFE_DataStaticRouterContext as DataStaticRouterContext,
UNSAFE_enhanceManualRouteObjects as enhanceManualRouteObjects,
} from "react-router-dom";

export interface StaticRouterProps {
Expand Down Expand Up @@ -198,11 +205,41 @@ function getStatelessNavigator() {
};
}

// Temporary manifest generation - we should optimize this by combining the
// tree-walks between convertRoutesToDataRoutes, enhanceManualRouteObjects,
// and generateManifest.
// Also look into getting rid of `route as AgnosticDataRouteObject` down below?
function generateManifest(
routes: DataRouteObject[],
manifest: Map<string, DataRouteObject> = new Map<string, DataRouteObject>()
): Map<string, RouteObject> {
routes.forEach((route) => {
manifest.set(route.id, route);
if (route.children) {
generateManifest(route.children, manifest);
}
});
return manifest;
}

export function unstable_createStaticRouter(
routes: RouteObject[],
context: StaticHandlerContext
): RemixRouter {
let dataRoutes = convertRoutesToDataRoutes(routes);
let dataRoutes = convertRoutesToDataRoutes(enhanceManualRouteObjects(routes));
let manifest = generateManifest(dataRoutes);

// Because our context matches may be from a framework-agnostic set of
// routes passed to createStaticHandler(), we update them here with our
// newly created/enhanced data routes
let matches = context.matches.map((match) => {
let route = manifest.get(match.route.id) || match.route;
return {
...match,
route: route as AgnosticDataRouteObject,
};
});

let msg = (method: string) =>
`You cannot use router.${method}() on the server because it is a stateless environment`;

Expand All @@ -214,7 +251,7 @@ export function unstable_createStaticRouter(
return {
historyAction: Action.Pop,
location: context.location,
matches: context.matches,
matches,
loaderData: context.loaderData,
actionData: context.actionData,
errors: context.errors,
Expand Down