diff --git a/.changeset/descendant-routes-data-errors.md b/.changeset/descendant-routes-data-errors.md new file mode 100644 index 0000000000..0d1949df3c --- /dev/null +++ b/.changeset/descendant-routes-data-errors.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Fix bug preventing rendering of descendant `` when `RouterProvider` errors existed diff --git a/package.json b/package.json index 167ae735df..43ac0c3dd9 100644 --- a/package.json +++ b/package.json @@ -108,10 +108,10 @@ "none": "45.8 kB" }, "packages/react-router/dist/react-router.production.min.js": { - "none": "13.5 kB" + "none": "13.6 kB" }, "packages/react-router/dist/umd/react-router.production.min.js": { - "none": "15.8 kB" + "none": "15.9 kB" }, "packages/react-router-dom/dist/react-router-dom.production.min.js": { "none": "12 kB" diff --git a/packages/react-router-dom/server.tsx b/packages/react-router-dom/server.tsx index e89006e066..8b21110069 100644 --- a/packages/react-router-dom/server.tsx +++ b/packages/react-router-dom/server.tsx @@ -18,12 +18,17 @@ import { UNSAFE_convertRoutesToDataRoutes as convertRoutesToDataRoutes, } from "@remix-run/router"; import { UNSAFE_mapRouteProperties as mapRouteProperties } from "react-router"; -import type { Location, RouteObject, To } from "react-router-dom"; -import { Routes } from "react-router-dom"; +import type { + DataRouteObject, + Location, + RouteObject, + To, +} from "react-router-dom"; import { createPath, parsePath, Router, + useRoutes, UNSAFE_DataRouterContext as DataRouterContext, UNSAFE_DataRouterStateContext as DataRouterStateContext, } from "react-router-dom"; @@ -127,7 +132,7 @@ export function StaticRouterProvider({ navigationType={dataRouterContext.router.state.historyAction} navigator={dataRouterContext.navigator} > - + @@ -142,6 +147,14 @@ export function StaticRouterProvider({ ); } +function DataRoutes({ + routes, +}: { + routes: DataRouteObject[]; +}): React.ReactElement | null { + return useRoutes(routes); +} + function serializeErrors( errors: StaticHandlerContext["errors"] ): StaticHandlerContext["errors"] { diff --git a/packages/react-router/__tests__/data-memory-router-test.tsx b/packages/react-router/__tests__/data-memory-router-test.tsx index da65602a64..b84537cf71 100644 --- a/packages/react-router/__tests__/data-memory-router-test.tsx +++ b/packages/react-router/__tests__/data-memory-router-test.tsx @@ -1019,11 +1019,6 @@ describe("createMemoryRouter", () => { ), { initialEntries: ["/deep/path/to/descendant/routes"], - hydrationData: { - loaderData: { - "0-0": "count=1", - }, - }, } ); let { container } = render(); @@ -1058,6 +1053,56 @@ describe("createMemoryRouter", () => { `); }); + it("renders alongside a data router ErrorBoundary", () => { + let router = createMemoryRouter( + [ + { + path: "*", + Component() { + return ( + <> + + + Descendant} /> + + + ); + }, + children: [ + { + id: "index", + index: true, + Component: () =>

Child

, + ErrorBoundary() { + return

{(useRouteError() as Error).message}

; + }, + }, + ], + }, + ], + { + initialEntries: ["/"], + hydrationData: { + errors: { + index: new Error("Broken!"), + }, + }, + } + ); + let { container } = render(); + + expect(getHtml(container)).toMatchInlineSnapshot(` + "
+

+ Broken! +

+

+ Descendant +

+
" + `); + }); + describe("errors", () => { it("renders hydration errors on leaf elements using errorElement", async () => { let router = createMemoryRouter( diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx index 7cdd4c083b..e5c9949dc8 100644 --- a/packages/react-router/lib/components.tsx +++ b/packages/react-router/lib/components.tsx @@ -116,7 +116,11 @@ export function RouterProvider({ navigationType={router.state.historyAction} navigator={navigator} > - {router.state.initialized ? : fallbackElement} + {router.state.initialized ? ( + + ) : ( + fallbackElement + )} @@ -125,6 +129,14 @@ export function RouterProvider({ ); } +function DataRoutes({ + routes, +}: { + routes: DataRouteObject[]; +}): React.ReactElement | null { + return useRoutes(routes); +} + export interface MemoryRouterProps { basename?: string; children?: React.ReactNode; @@ -393,15 +405,7 @@ export function Routes({ children, location, }: RoutesProps): React.ReactElement | null { - let dataRouterContext = React.useContext(DataRouterContext); - // When in a DataRouterContext _without_ children, we use the router routes - // directly. If we have children, then we're in a descendant tree and we - // need to use child routes. - let routes = - dataRouterContext && !children - ? (dataRouterContext.router.routes as DataRouteObject[]) - : createRoutesFromChildren(children); - return useRoutes(routes, location); + return useRoutes(createRoutesFromChildren(children), location); } export interface AwaitResolveRenderFunction { diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 3daea2283d..ce5353b68b 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -320,6 +320,7 @@ export function useRoutes( ); let { navigator } = React.useContext(NavigationContext); + let dataRouterContext = React.useContext(DataRouterContext); let dataRouterStateContext = React.useContext(DataRouterStateContext); let { matches: parentMatches } = React.useContext(RouteContext); let routeMatch = parentMatches[parentMatches.length - 1]; @@ -433,7 +434,10 @@ export function useRoutes( }) ), parentMatches, - dataRouterStateContext || undefined + // Only pass along the dataRouterStateContext when we're rendering from the + // RouterProvider layer. If routes is different then we're rendering from + // a descendant tree + dataRouterContext?.router.routes === routes ? dataRouterStateContext : null ); // When a user passes in a `locationArg`, the associated routes need to @@ -622,7 +626,7 @@ function RenderedRoute({ routeContext, match, children }: RenderedRouteProps) { export function _renderMatches( matches: RouteMatch[] | null, parentMatches: RouteMatch[] = [], - dataRouterState?: RemixRouter["state"] + dataRouterState: RemixRouter["state"] | null = null ): React.ReactElement | null { if (matches == null) { if (dataRouterState?.errors) { @@ -644,7 +648,9 @@ export function _renderMatches( ); invariant( errorIndex >= 0, - `Could not find a matching route for the current errors: ${errors}` + `Could not find a matching route for errors on route IDs: ${Object.keys( + errors + ).join(",")}` ); renderedMatches = renderedMatches.slice( 0,