Skip to content

Commit 2821817

Browse files
authored
Fix descendant Routes rendering alongside RouterProvider errors (#10374)
1 parent fda0123 commit 2821817

File tree

6 files changed

+96
-23
lines changed

6 files changed

+96
-23
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Fix bug preventing rendering of descendant `<Routes>` when `RouterProvider` errors existed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@
108108
"none": "45.8 kB"
109109
},
110110
"packages/react-router/dist/react-router.production.min.js": {
111-
"none": "13.5 kB"
111+
"none": "13.6 kB"
112112
},
113113
"packages/react-router/dist/umd/react-router.production.min.js": {
114-
"none": "15.8 kB"
114+
"none": "15.9 kB"
115115
},
116116
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
117117
"none": "12 kB"

packages/react-router-dom/server.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ import {
1818
UNSAFE_convertRoutesToDataRoutes as convertRoutesToDataRoutes,
1919
} from "@remix-run/router";
2020
import { UNSAFE_mapRouteProperties as mapRouteProperties } from "react-router";
21-
import type { Location, RouteObject, To } from "react-router-dom";
22-
import { Routes } from "react-router-dom";
21+
import type {
22+
DataRouteObject,
23+
Location,
24+
RouteObject,
25+
To,
26+
} from "react-router-dom";
2327
import {
2428
createPath,
2529
parsePath,
2630
Router,
31+
useRoutes,
2732
UNSAFE_DataRouterContext as DataRouterContext,
2833
UNSAFE_DataRouterStateContext as DataRouterStateContext,
2934
} from "react-router-dom";
@@ -127,7 +132,7 @@ export function StaticRouterProvider({
127132
navigationType={dataRouterContext.router.state.historyAction}
128133
navigator={dataRouterContext.navigator}
129134
>
130-
<Routes />
135+
<DataRoutes routes={router.routes} />
131136
</Router>
132137
</DataRouterStateContext.Provider>
133138
</DataRouterContext.Provider>
@@ -142,6 +147,14 @@ export function StaticRouterProvider({
142147
);
143148
}
144149

150+
function DataRoutes({
151+
routes,
152+
}: {
153+
routes: DataRouteObject[];
154+
}): React.ReactElement | null {
155+
return useRoutes(routes);
156+
}
157+
145158
function serializeErrors(
146159
errors: StaticHandlerContext["errors"]
147160
): StaticHandlerContext["errors"] {

packages/react-router/__tests__/data-memory-router-test.tsx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,11 +1019,6 @@ describe("createMemoryRouter", () => {
10191019
),
10201020
{
10211021
initialEntries: ["/deep/path/to/descendant/routes"],
1022-
hydrationData: {
1023-
loaderData: {
1024-
"0-0": "count=1",
1025-
},
1026-
},
10271022
}
10281023
);
10291024
let { container } = render(<RouterProvider router={router} />);
@@ -1058,6 +1053,56 @@ describe("createMemoryRouter", () => {
10581053
`);
10591054
});
10601055

1056+
it("renders <Routes> alongside a data router ErrorBoundary", () => {
1057+
let router = createMemoryRouter(
1058+
[
1059+
{
1060+
path: "*",
1061+
Component() {
1062+
return (
1063+
<>
1064+
<Outlet />
1065+
<Routes>
1066+
<Route index element={<h1>Descendant</h1>} />
1067+
</Routes>
1068+
</>
1069+
);
1070+
},
1071+
children: [
1072+
{
1073+
id: "index",
1074+
index: true,
1075+
Component: () => <h1>Child</h1>,
1076+
ErrorBoundary() {
1077+
return <p>{(useRouteError() as Error).message}</p>;
1078+
},
1079+
},
1080+
],
1081+
},
1082+
],
1083+
{
1084+
initialEntries: ["/"],
1085+
hydrationData: {
1086+
errors: {
1087+
index: new Error("Broken!"),
1088+
},
1089+
},
1090+
}
1091+
);
1092+
let { container } = render(<RouterProvider router={router} />);
1093+
1094+
expect(getHtml(container)).toMatchInlineSnapshot(`
1095+
"<div>
1096+
<p>
1097+
Broken!
1098+
</p>
1099+
<h1>
1100+
Descendant
1101+
</h1>
1102+
</div>"
1103+
`);
1104+
});
1105+
10611106
describe("errors", () => {
10621107
it("renders hydration errors on leaf elements using errorElement", async () => {
10631108
let router = createMemoryRouter(

packages/react-router/lib/components.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ export function RouterProvider({
116116
navigationType={router.state.historyAction}
117117
navigator={navigator}
118118
>
119-
{router.state.initialized ? <Routes /> : fallbackElement}
119+
{router.state.initialized ? (
120+
<DataRoutes routes={router.routes} />
121+
) : (
122+
fallbackElement
123+
)}
120124
</Router>
121125
</DataRouterStateContext.Provider>
122126
</DataRouterContext.Provider>
@@ -125,6 +129,14 @@ export function RouterProvider({
125129
);
126130
}
127131

132+
function DataRoutes({
133+
routes,
134+
}: {
135+
routes: DataRouteObject[];
136+
}): React.ReactElement | null {
137+
return useRoutes(routes);
138+
}
139+
128140
export interface MemoryRouterProps {
129141
basename?: string;
130142
children?: React.ReactNode;
@@ -393,15 +405,7 @@ export function Routes({
393405
children,
394406
location,
395407
}: RoutesProps): React.ReactElement | null {
396-
let dataRouterContext = React.useContext(DataRouterContext);
397-
// When in a DataRouterContext _without_ children, we use the router routes
398-
// directly. If we have children, then we're in a descendant tree and we
399-
// need to use child routes.
400-
let routes =
401-
dataRouterContext && !children
402-
? (dataRouterContext.router.routes as DataRouteObject[])
403-
: createRoutesFromChildren(children);
404-
return useRoutes(routes, location);
408+
return useRoutes(createRoutesFromChildren(children), location);
405409
}
406410

407411
export interface AwaitResolveRenderFunction {

packages/react-router/lib/hooks.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ export function useRoutes(
320320
);
321321

322322
let { navigator } = React.useContext(NavigationContext);
323+
let dataRouterContext = React.useContext(DataRouterContext);
323324
let dataRouterStateContext = React.useContext(DataRouterStateContext);
324325
let { matches: parentMatches } = React.useContext(RouteContext);
325326
let routeMatch = parentMatches[parentMatches.length - 1];
@@ -433,7 +434,10 @@ export function useRoutes(
433434
})
434435
),
435436
parentMatches,
436-
dataRouterStateContext || undefined
437+
// Only pass along the dataRouterStateContext when we're rendering from the
438+
// RouterProvider layer. If routes is different then we're rendering from
439+
// a descendant <Routes> tree
440+
dataRouterContext?.router.routes === routes ? dataRouterStateContext : null
437441
);
438442

439443
// When a user passes in a `locationArg`, the associated routes need to
@@ -622,7 +626,7 @@ function RenderedRoute({ routeContext, match, children }: RenderedRouteProps) {
622626
export function _renderMatches(
623627
matches: RouteMatch[] | null,
624628
parentMatches: RouteMatch[] = [],
625-
dataRouterState?: RemixRouter["state"]
629+
dataRouterState: RemixRouter["state"] | null = null
626630
): React.ReactElement | null {
627631
if (matches == null) {
628632
if (dataRouterState?.errors) {
@@ -644,7 +648,9 @@ export function _renderMatches(
644648
);
645649
invariant(
646650
errorIndex >= 0,
647-
`Could not find a matching route for the current errors: ${errors}`
651+
`Could not find a matching route for errors on route IDs: ${Object.keys(
652+
errors
653+
).join(",")}`
648654
);
649655
renderedMatches = renderedMatches.slice(
650656
0,

0 commit comments

Comments
 (0)