From e23d6cb9c363182bcfc065748f4f2f8e118bdf5a Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Thu, 23 Jan 2025 11:04:18 -0500 Subject: [PATCH] Optimization to avoid redundant calls to `matchRoutes` (#12800) --- .changeset/smooth-mangos-act.md | 5 +++++ .../__tests__/dom/ssr/components-test.tsx | 7 ++++--- packages/react-router/lib/hooks.tsx | 8 +++++++- packages/react-router/lib/router/router.ts | 11 ++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 .changeset/smooth-mangos-act.md diff --git a/.changeset/smooth-mangos-act.md b/.changeset/smooth-mangos-act.md new file mode 100644 index 0000000000..8400a80794 --- /dev/null +++ b/.changeset/smooth-mangos-act.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Optimize route matching by skipping redundant `matchRoutes` calls when possible diff --git a/packages/react-router/__tests__/dom/ssr/components-test.tsx b/packages/react-router/__tests__/dom/ssr/components-test.tsx index 952af47050..e5fcfb9ef9 100644 --- a/packages/react-router/__tests__/dom/ssr/components-test.tsx +++ b/packages/react-router/__tests__/dom/ssr/components-test.tsx @@ -150,9 +150,10 @@ describe("", () => { describe("", () => { it("handles empty default export objects from the compiler", async () => { - let staticHandlerContext = await createStaticHandler([{ path: "/" }]).query( - new Request("http://localhost/") - ); + let staticHandlerContext = await createStaticHandler([ + { id: "root", path: "/", children: [{ id: "empty", index: true }] }, + ]).query(new Request("http://localhost/")); + invariant( !(staticHandlerContext instanceof Response), "Expected a context" diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 13027e2da9..911a490a1e 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -31,6 +31,7 @@ import type { } from "./router/router"; import { IDLE_BLOCKER } from "./router/router"; import type { + AgnosticRouteMatch, ParamParseKey, Params, PathMatch, @@ -534,7 +535,12 @@ export function useRoutesImpl( remainingPathname = "/" + segments.slice(parentSegments.length).join("/"); } - let matches = matchRoutes(routes, { pathname: remainingPathname }); + let matches = + dataRouterState && + dataRouterState.matches && + dataRouterState.matches.length > 0 + ? (dataRouterState.matches as AgnosticRouteMatch[]) + : matchRoutes(routes, { pathname: remainingPathname }); if (ENABLE_DEV_WARNINGS) { warning( diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 86fb8527a2..be0fe6ec48 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -838,6 +838,7 @@ export function createRouter(init: RouterInit): Router { let initialScrollRestored = init.hydrationData != null; let initialMatches = matchRoutes(dataRoutes, init.history.location, basename); + let initialMatchesIsFOW = false; let initialErrors: RouteData | null = null; if (initialMatches == null && !patchRoutesOnNavigationImpl) { @@ -882,6 +883,7 @@ export function createRouter(init: RouterInit): Router { init.history.location.pathname ); if (fogOfWar.active && fogOfWar.matches) { + initialMatchesIsFOW = true; initialMatches = fogOfWar.matches; } } else if (initialMatches.some((m) => m.route.lazy)) { @@ -1521,7 +1523,14 @@ export function createRouter(init: RouterInit): Router { let routesToUse = inFlightDataRoutes || dataRoutes; let loadingNavigation = opts && opts.overrideNavigation; - let matches = matchRoutes(routesToUse, location, basename); + let matches = + opts?.initialHydration && + state.matches && + state.matches.length > 0 && + !initialMatchesIsFOW + ? // `matchRoutes()` has already been called if we're in here via `router.initialize()` + state.matches + : matchRoutes(routesToUse, location, basename); let flushSync = (opts && opts.flushSync) === true; let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);