diff --git a/CHANGELOG.md b/CHANGELOG.md index 3613f95b08..a22e844987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,161 +13,163 @@ We manage release notes in this file instead of the paginated Github Releases Pa Table of Contents - [React Router Releases](#react-router-releases) + - [v6.24.1](#v6241) + - [Patch Changes](#patch-changes) - [v6.24.0](#v6240) - [What's Changed](#whats-changed) - [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war) - [Minor Changes](#minor-changes) - - [Patch Changes](#patch-changes) - - [v6.23.1](#v6231) - [Patch Changes](#patch-changes-1) + - [v6.23.1](#v6231) + - [Patch Changes](#patch-changes-2) - [v6.23.0](#v6230) - [What's Changed](#whats-changed-1) - [Data Strategy (unstable)](#data-strategy-unstable) - [Skip Action Error Revalidation (unstable)](#skip-action-error-revalidation-unstable) - [Minor Changes](#minor-changes-1) - [v6.22.3](#v6223) - - [Patch Changes](#patch-changes-2) - - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-3) - - [v6.22.1](#v6221) + - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-4) + - [v6.22.1](#v6221) + - [Patch Changes](#patch-changes-5) - [v6.22.0](#v6220) - [What's Changed](#whats-changed-2) - [Core Web Vitals Technology Report Flag](#core-web-vitals-technology-report-flag) - [Minor Changes](#minor-changes-2) - - [Patch Changes](#patch-changes-5) - - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-6) - - [v6.21.2](#v6212) + - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-7) - - [v6.21.1](#v6211) + - [v6.21.2](#v6212) - [Patch Changes](#patch-changes-8) + - [v6.21.1](#v6211) + - [Patch Changes](#patch-changes-9) - [v6.21.0](#v6210) - [What's Changed](#whats-changed-3) - [`future.v7_relativeSplatPath`](#futurev7_relativesplatpath) - [Partial Hydration](#partial-hydration) - [Minor Changes](#minor-changes-3) - - [Patch Changes](#patch-changes-9) - - [v6.20.1](#v6201) - [Patch Changes](#patch-changes-10) + - [v6.20.1](#v6201) + - [Patch Changes](#patch-changes-11) - [v6.20.0](#v6200) - [Minor Changes](#minor-changes-4) - - [Patch Changes](#patch-changes-11) + - [Patch Changes](#patch-changes-12) - [v6.19.0](#v6190) - [What's Changed](#whats-changed-4) - [`unstable_flushSync` API](#unstable_flushsync-api) - [Minor Changes](#minor-changes-5) - - [Patch Changes](#patch-changes-12) + - [Patch Changes](#patch-changes-13) - [v6.18.0](#v6180) - [What's Changed](#whats-changed-5) - [New Fetcher APIs](#new-fetcher-apis) - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist) - [Minor Changes](#minor-changes-6) - - [Patch Changes](#patch-changes-13) + - [Patch Changes](#patch-changes-14) - [v6.17.0](#v6170) - [What's Changed](#whats-changed-6) - [View Transitions 🚀](#view-transitions-) - [Minor Changes](#minor-changes-7) - - [Patch Changes](#patch-changes-14) + - [Patch Changes](#patch-changes-15) - [v6.16.0](#v6160) - [Minor Changes](#minor-changes-8) - - [Patch Changes](#patch-changes-15) + - [Patch Changes](#patch-changes-16) - [v6.15.0](#v6150) - [Minor Changes](#minor-changes-9) - - [Patch Changes](#patch-changes-16) - - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-17) - - [v6.14.1](#v6141) + - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-18) + - [v6.14.1](#v6141) + - [Patch Changes](#patch-changes-19) - [v6.14.0](#v6140) - [What's Changed](#whats-changed-7) - [JSON/Text Submissions](#jsontext-submissions) - [Minor Changes](#minor-changes-10) - - [Patch Changes](#patch-changes-19) + - [Patch Changes](#patch-changes-20) - [v6.13.0](#v6130) - [What's Changed](#whats-changed-8) - [`future.v7_startTransition`](#futurev7_starttransition) - [Minor Changes](#minor-changes-11) - - [Patch Changes](#patch-changes-20) - - [v6.12.1](#v6121) - [Patch Changes](#patch-changes-21) + - [v6.12.1](#v6121) + - [Patch Changes](#patch-changes-22) - [v6.12.0](#v6120) - [What's Changed](#whats-changed-9) - [`React.startTransition` support](#reactstarttransition-support) - [Minor Changes](#minor-changes-12) - - [Patch Changes](#patch-changes-22) - - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-23) - - [v6.11.1](#v6111) + - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-24) + - [v6.11.1](#v6111) + - [Patch Changes](#patch-changes-25) - [v6.11.0](#v6110) - [Minor Changes](#minor-changes-13) - - [Patch Changes](#patch-changes-25) + - [Patch Changes](#patch-changes-26) - [v6.10.0](#v6100) - [What's Changed](#whats-changed-10) - [Minor Changes](#minor-changes-14) - [`future.v7_normalizeFormMethod`](#futurev7_normalizeformmethod) - - [Patch Changes](#patch-changes-26) + - [Patch Changes](#patch-changes-27) - [v6.9.0](#v690) - [What's Changed](#whats-changed-11) - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties) - [Introducing Lazy Route Modules](#introducing-lazy-route-modules) - [Minor Changes](#minor-changes-15) - - [Patch Changes](#patch-changes-27) - - [v6.8.2](#v682) - [Patch Changes](#patch-changes-28) - - [v6.8.1](#v681) + - [v6.8.2](#v682) - [Patch Changes](#patch-changes-29) + - [v6.8.1](#v681) + - [Patch Changes](#patch-changes-30) - [v6.8.0](#v680) - [Minor Changes](#minor-changes-16) - - [Patch Changes](#patch-changes-30) + - [Patch Changes](#patch-changes-31) - [v6.7.0](#v670) - [Minor Changes](#minor-changes-17) - - [Patch Changes](#patch-changes-31) - - [v6.6.2](#v662) - [Patch Changes](#patch-changes-32) - - [v6.6.1](#v661) + - [v6.6.2](#v662) - [Patch Changes](#patch-changes-33) + - [v6.6.1](#v661) + - [Patch Changes](#patch-changes-34) - [v6.6.0](#v660) - [What's Changed](#whats-changed-12) - [Minor Changes](#minor-changes-18) - - [Patch Changes](#patch-changes-34) + - [Patch Changes](#patch-changes-35) - [v6.5.0](#v650) - [What's Changed](#whats-changed-13) - [Minor Changes](#minor-changes-19) - - [Patch Changes](#patch-changes-35) - - [v6.4.5](#v645) - [Patch Changes](#patch-changes-36) - - [v6.4.4](#v644) + - [v6.4.5](#v645) - [Patch Changes](#patch-changes-37) - - [v6.4.3](#v643) + - [v6.4.4](#v644) - [Patch Changes](#patch-changes-38) - - [v6.4.2](#v642) + - [v6.4.3](#v643) - [Patch Changes](#patch-changes-39) - - [v6.4.1](#v641) + - [v6.4.2](#v642) - [Patch Changes](#patch-changes-40) + - [v6.4.1](#v641) + - [Patch Changes](#patch-changes-41) - [v6.4.0](#v640) - [What's Changed](#whats-changed-14) - [Remix Data APIs](#remix-data-apis) - - [Patch Changes](#patch-changes-41) + - [Patch Changes](#patch-changes-42) - [v6.3.0](#v630) - [Minor Changes](#minor-changes-20) - [v6.2.2](#v622) - - [Patch Changes](#patch-changes-42) - - [v6.2.1](#v621) - [Patch Changes](#patch-changes-43) + - [v6.2.1](#v621) + - [Patch Changes](#patch-changes-44) - [v6.2.0](#v620) - [Minor Changes](#minor-changes-21) - - [Patch Changes](#patch-changes-44) - - [v6.1.1](#v611) - [Patch Changes](#patch-changes-45) + - [v6.1.1](#v611) + - [Patch Changes](#patch-changes-46) - [v6.1.0](#v610) - [Minor Changes](#minor-changes-22) - - [Patch Changes](#patch-changes-46) - - [v6.0.2](#v602) - [Patch Changes](#patch-changes-47) - - [v6.0.1](#v601) + - [v6.0.2](#v602) - [Patch Changes](#patch-changes-48) + - [v6.0.1](#v601) + - [Patch Changes](#patch-changes-49) - [v6.0.0](#v600) @@ -191,6 +193,21 @@ Date: YYYY-MM-DD **Full Changelog**: [`v6.X.Y...v6.X.Y`](https://github.com/remix-run/react-router/compare/react-router@6.X.Y...react-router@6.X.Y) --> +## v6.24.1 + +Date: 2024-07-03 + +### Patch Changes + +- Remove `polyfill.io` reference from warning message because the domain was sold and has since been determined to serve malware ([#11741](https://github.com/remix-run/react-router/pull/11741)) + - See https://sansec.io/research/polyfill-supply-chain-attack +- Export `NavLinkRenderProps` type for easier typing of custom `NavLink` callback ([#11553](https://github.com/remix-run/react-router/pull/11553)) +- When using `future.v7_relativeSplatPath`, properly resolve relative paths in splat routes that are children of pathless routes ([#11633](https://github.com/remix-run/react-router/pull/11633)) +- Fog of War (unstable): Trigger a new `router.routes` identity/reflow during route patching ([#11740](https://github.com/remix-run/react-router/pull/11740)) +- Fog of War (unstable): Fix initial matching when a splat route matches ([#11759](https://github.com/remix-run/react-router/pull/11759)) + +**Full Changelog**: [`v6.24.0...v6.24.1`](https://github.com/remix-run/react-router/compare/react-router@6.24.0...react-router@6.24.1) + ## v6.24.0 Date: 2024-06-24 diff --git a/contributors.yml b/contributors.yml index f900da385e..ea24af9bbd 100644 --- a/contributors.yml +++ b/contributors.yml @@ -192,6 +192,7 @@ - modex98 - morleytatro - ms10596 +- mspiess - mtliendo - ned-park - nilubisan @@ -276,4 +277,5 @@ - yionr - yracnet - yuleicul +- zeromask1337 - zheng-chuang diff --git a/package.json b/package.json index 8ce0ca6a44..65578ab547 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ }, "filesize": { "packages/router/dist/router.umd.min.js": { - "none": "56.3 kB" + "none": "56.4 kB" }, "packages/react-router/dist/react-router.production.min.js": { "none": "14.9 kB" diff --git a/packages/react-router-dom-v5-compat/CHANGELOG.md b/packages/react-router-dom-v5-compat/CHANGELOG.md index e661a1f149..26289f34f5 100644 --- a/packages/react-router-dom-v5-compat/CHANGELOG.md +++ b/packages/react-router-dom-v5-compat/CHANGELOG.md @@ -1,5 +1,14 @@ # `react-router-dom-v5-compat` +## 6.24.1 + +### Patch Changes + +- Updated dependencies: + - `react-router-dom@6.24.1` + - `@remix-run/router@1.17.1` + - `react-router@6.24.1` + ## 6.24.0 ### Patch Changes diff --git a/packages/react-router-dom-v5-compat/package.json b/packages/react-router-dom-v5-compat/package.json index 16ada3792c..8f77e51bed 100644 --- a/packages/react-router-dom-v5-compat/package.json +++ b/packages/react-router-dom-v5-compat/package.json @@ -1,6 +1,6 @@ { "name": "react-router-dom-v5-compat", - "version": "6.24.0", + "version": "6.24.1", "description": "Migration path to React Router v6 from v4/5", "keywords": [ "react", diff --git a/packages/react-router-dom/CHANGELOG.md b/packages/react-router-dom/CHANGELOG.md index 3cdf61b89d..18cbd631a4 100644 --- a/packages/react-router-dom/CHANGELOG.md +++ b/packages/react-router-dom/CHANGELOG.md @@ -1,5 +1,16 @@ # `react-router-dom` +## 6.24.1 + +### Patch Changes + +- Remove `polyfill.io` reference from warning message because the domain was sold and has since been determined to serve malware ([#11741](https://github.com/remix-run/react-router/pull/11741)) + - See +- Export `NavLinkRenderProps` type for easier typing of custom `NavLink` callback ([#11553](https://github.com/remix-run/react-router/pull/11553)) +- Updated dependencies: + - `@remix-run/router@1.17.1` + - `react-router@6.24.1` + ## 6.24.0 ### Minor Changes diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 0718b318c0..77e6727e9e 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -1024,7 +1024,7 @@ if (__DEV__) { Link.displayName = "Link"; } -type NavLinkRenderProps = { +export type NavLinkRenderProps = { isActive: boolean; isPending: boolean; isTransitioning: boolean; @@ -1464,11 +1464,7 @@ export function useSearchParams( `You cannot use the \`useSearchParams\` hook in a browser that does not ` + `support the URLSearchParams API. If you need to support Internet ` + `Explorer 11, we recommend you load a polyfill such as ` + - `https://github.com/ungap/url-search-params\n\n` + - `If you're unsure how to load polyfills, we recommend you check out ` + - `https://polyfill.io/v3/ which provides some recommendations about how ` + - `to load polyfills only for users that need them, instead of for every ` + - `user.` + `https://github.com/ungap/url-search-params.` ); let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit)); diff --git a/packages/react-router-dom/package.json b/packages/react-router-dom/package.json index 510f8e5b18..007b2a03ed 100644 --- a/packages/react-router-dom/package.json +++ b/packages/react-router-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-router-dom", - "version": "6.24.0", + "version": "6.24.1", "description": "Declarative routing for React web applications", "keywords": [ "react", diff --git a/packages/react-router-native/CHANGELOG.md b/packages/react-router-native/CHANGELOG.md index 6ef1db310b..b0d792e82d 100644 --- a/packages/react-router-native/CHANGELOG.md +++ b/packages/react-router-native/CHANGELOG.md @@ -1,5 +1,12 @@ # `react-router-native` +## 6.24.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@6.24.1` + ## 6.24.0 ### Patch Changes diff --git a/packages/react-router-native/package.json b/packages/react-router-native/package.json index 9582990289..02536385b4 100644 --- a/packages/react-router-native/package.json +++ b/packages/react-router-native/package.json @@ -1,6 +1,6 @@ { "name": "react-router-native", - "version": "6.24.0", + "version": "6.24.1", "description": "Declarative routing for React Native applications", "keywords": [ "react", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 84b6b33fa8..aae5d0795f 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -1,5 +1,13 @@ # `react-router` +## 6.24.1 + +### Patch Changes + +- When using `future.v7_relativeSplatPath`, properly resolve relative paths in splat routes that are children of pathless routes ([#11633](https://github.com/remix-run/react-router/pull/11633)) +- Updated dependencies: + - `@remix-run/router@1.17.1` + ## 6.24.0 ### Minor Changes diff --git a/packages/react-router/__tests__/useResolvedPath-test.tsx b/packages/react-router/__tests__/useResolvedPath-test.tsx index e9ca3ac63e..729503a8fa 100644 --- a/packages/react-router/__tests__/useResolvedPath-test.tsx +++ b/packages/react-router/__tests__/useResolvedPath-test.tsx @@ -420,6 +420,43 @@ describe("useResolvedPath", () => { " `); }); + + // gh-issue #11629 + it("when enabled, '.' resolves to the current path including any splat paths nested in pathless routes", () => { + let { container } = render( + + + + + + } + /> + + + + + ); + let html = getHtml(container); + html = html ? html.replace(/</g, "<").replace(/>/g, ">") : html; + expect(html).toMatchInlineSnapshot(` + "
+ --- Routes: --- + useLocation(): /foo/bar + useResolvedPath('.'): /foo/bar + useResolvedPath('..'): /foo + useResolvedPath('..', { relative: 'path' }): /foo + useResolvedPath('baz/qux'): /foo/bar/baz/qux + useResolvedPath('./baz/qux'): /foo/bar/baz/qux + +
" + `); + }); }); }); diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 021a6a1209..812f3e0c59 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "react-router", - "version": "6.24.0", + "version": "6.24.1", "description": "Declarative routing for React", "keywords": [ "react", diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 850224768f..0dece7e675 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -1,5 +1,12 @@ # `@remix-run/router` +## 1.17.1 + +### Patch Changes + +- Fog of War (unstable): Trigger a new `router.routes` identity/reflow during route patching ([#11740](https://github.com/remix-run/react-router/pull/11740)) +- Fog of War (unstable): Fix initial matching when a splat route matches ([#11759](https://github.com/remix-run/react-router/pull/11759)) + ## 1.17.0 ### Minor Changes diff --git a/packages/router/__tests__/lazy-discovery-test.ts b/packages/router/__tests__/lazy-discovery-test.ts index 690a586014..0ae50b9fcc 100644 --- a/packages/router/__tests__/lazy-discovery-test.ts +++ b/packages/router/__tests__/lazy-discovery-test.ts @@ -541,6 +541,34 @@ describe("Lazy Route Discovery (Fog of War)", () => { expect(router.state.matches.map((m) => m.route.id)).toEqual(["a", "b"]); }); + it("de-prioritizes splat routes in favor of looking for better async matches (splat/*)", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + id: "splat", + path: "/splat/*", + }, + ], + async unstable_patchRoutesOnMiss({ matches, patch }) { + await tick(); + patch(null, [ + { + id: "static", + path: "/splat/static", + }, + ]); + }, + }); + + await router.navigate("/splat/static"); + expect(router.state.location.pathname).toBe("/splat/static"); + expect(router.state.matches.map((m) => m.route.id)).toEqual(["static"]); + }); + it("matches splats when other paths don't pan out", async () => { router = createRouter({ history: createMemoryHistory(), @@ -628,6 +656,39 @@ describe("Lazy Route Discovery (Fog of War)", () => { ]); }); + it("discovers routes during initial hydration when a splat route matches", async () => { + let childrenDfd = createDeferred(); + + router = createRouter({ + history: createMemoryHistory({ initialEntries: ["/test"] }), + routes: [ + { + path: "/", + }, + { + path: "*", + }, + ], + async unstable_patchRoutesOnMiss({ path, patch, matches }) { + let children = await childrenDfd.promise; + patch(null, children); + }, + }); + router.initialize(); + expect(router.state.initialized).toBe(false); + + childrenDfd.resolve([ + { + id: "test", + path: "/test", + }, + ]); + await tick(); + expect(router.state.initialized).toBe(true); + expect(router.state.location.pathname).toBe("/test"); + expect(router.state.matches.map((m) => m.route.id)).toEqual(["test"]); + }); + it("discovers new root routes", async () => { let childrenDfd = createDeferred(); let childLoaderDfd = createDeferred(); @@ -737,7 +798,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { let childLoaderDfd = createDeferred(); router = createRouter({ - history: createMemoryHistory(), + history: createMemoryHistory({ initialEntries: ["/other"] }), routes: [ { id: "other", @@ -835,6 +896,80 @@ describe("Lazy Route Discovery (Fog of War)", () => { ]); }); + it("creates a new router.routes identity when patching routes", async () => { + let childrenDfd = createDeferred(); + + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + id: "parent", + path: "parent", + }, + ], + async unstable_patchRoutesOnMiss({ patch }) { + let children = await childrenDfd.promise; + patch("parent", children); + }, + }); + let originalRoutes = router.routes; + + router.navigate("/parent/child"); + childrenDfd.resolve([ + { + id: "child", + path: "child", + }, + ]); + await tick(); + + expect(router.state.location.pathname).toBe("/parent/child"); + expect(router.state.matches.map((m) => m.route.id)).toEqual([ + "parent", + "child", + ]); + + expect(router.routes).not.toBe(originalRoutes); + }); + + it("allows patching externally/eagerly and triggers a reflow", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + id: "parent", + path: "parent", + }, + ], + }); + let spy = jest.fn(); + let unsubscribe = router.subscribe(spy); + let originalRoutes = router.routes; + router.patchRoutes("parent", [ + { + id: "child", + path: "child", + }, + ]); + expect(spy).toHaveBeenCalled(); + expect(router.routes).not.toBe(originalRoutes); + + await router.navigate("/parent/child"); + expect(router.state.location.pathname).toBe("/parent/child"); + expect(router.state.matches.map((m) => m.route.id)).toEqual([ + "parent", + "child", + ]); + + unsubscribe(); + }); + describe("errors", () => { it("lazy 404s (GET navigation)", async () => { let childrenDfd = createDeferred(); diff --git a/packages/router/package.json b/packages/router/package.json index b58c4435de..a133c23194 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@remix-run/router", - "version": "1.17.0", + "version": "1.17.1", "description": "Nested/Data-driven/Framework-agnostic Routing", "keywords": [ "remix", diff --git a/packages/router/router.ts b/packages/router/router.ts index 9fbca292c0..e6c0ff03b6 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -249,7 +249,8 @@ export interface Router { * PRIVATE DO NOT USE * * Patch additional children routes into an existing parent route - * @param routeId The parent route id + * @param routeId The parent route id or a callback function accepting `patch` + * to perform batch patching * @param children The additional children routes */ patchRoutes(routeId: string | null, children: AgnosticRouteObject[]): void; @@ -840,6 +841,20 @@ export function createRouter(init: RouterInit): Router { initialErrors = { [route.id]: error }; } + // If the user provided a patchRoutesOnMiss implementation and our initial + // match is a splat route, clear them out so we run through lazy discovery + // on hydration in case there's a more accurate lazy route match + if (initialMatches && patchRoutesOnMissImpl) { + let fogOfWar = checkFogOfWar( + initialMatches, + dataRoutes, + init.history.location.pathname + ); + if (fogOfWar.active) { + initialMatches = null; + } + } + let initialized: boolean; if (!initialMatches) { // We need to run patchRoutesOnMiss in initialize() @@ -1222,6 +1237,7 @@ export function createRouter(init: RouterInit): Router { isMutationMethod(state.navigation.formMethod) && location.state?._isRedirect !== true); + // Commit any in-flight routes at the end of the HMR revalidation "navigation" if (inFlightDataRoutes) { dataRoutes = inFlightDataRoutes; inFlightDataRoutes = undefined; @@ -3160,7 +3176,10 @@ export function createRouter(init: RouterInit): Router { return { active: true, matches: fogMatches || [] }; } else { let leafRoute = matches[matches.length - 1].route; - if (leafRoute.path === "*") { + if ( + leafRoute.path && + (leafRoute.path === "*" || leafRoute.path.endsWith("/*")) + ) { // If we matched a splat, it might only be because we haven't yet fetched // the children that would match with a higher score, so let's fetch // around and find out @@ -3204,12 +3223,14 @@ export function createRouter(init: RouterInit): Router { ? partialMatches[partialMatches.length - 1].route : null; while (true) { + let isNonHMR = inFlightDataRoutes == null; + let routesToUse = inFlightDataRoutes || dataRoutes; try { await loadLazyRouteChildren( patchRoutesOnMissImpl!, pathname, partialMatches, - dataRoutes || inFlightDataRoutes, + routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, @@ -3217,13 +3238,22 @@ export function createRouter(init: RouterInit): Router { ); } catch (e) { return { type: "error", error: e, partialMatches }; + } finally { + // If we are not in the middle of an HMR revalidation and we changed the + // routes, provide a new identity so when we `updateState` at the end of + // this navigation/fetch `router.routes` will be a new identity and + // trigger a re-run of memoized `router.routes` dependencies. + // HMR will already update the identity and reflow when it lands + // `inFlightDataRoutes` in `completeNavigation` + if (isNonHMR) { + dataRoutes = [...dataRoutes]; + } } if (signal.aborted) { return { type: "aborted" }; } - let routesToUse = inFlightDataRoutes || dataRoutes; let newMatches = matchRoutes(routesToUse, pathname, basename); let matchedSplat = false; if (newMatches) { @@ -3284,6 +3314,31 @@ export function createRouter(init: RouterInit): Router { ); } + function patchRoutes( + routeId: string | null, + children: AgnosticRouteObject[] + ): void { + let isNonHMR = inFlightDataRoutes == null; + let routesToUse = inFlightDataRoutes || dataRoutes; + patchRoutesImpl( + routeId, + children, + routesToUse, + manifest, + mapRouteProperties + ); + + // If we are not in the middle of an HMR revalidation and we changed the + // routes, provide a new identity and trigger a reflow via `updateState` + // to re-run memoized `router.routes` dependencies. + // HMR will already update the identity and reflow when it lands + // `inFlightDataRoutes` in `completeNavigation` + if (isNonHMR) { + dataRoutes = [...dataRoutes]; + updateState({}); + } + } + router = { get basename() { return basename; @@ -3315,15 +3370,7 @@ export function createRouter(init: RouterInit): Router { dispose, getBlocker, deleteBlocker, - patchRoutes(routeId, children) { - return patchRoutes( - routeId, - children, - dataRoutes || inFlightDataRoutes, - manifest, - mapRouteProperties - ); - }, + patchRoutes, _internalFetchControllers: fetchControllers, _internalActiveDeferreds: activeDeferreds, // TODO: Remove setRoutes, it's temporary to avoid dealing with @@ -4488,7 +4535,7 @@ function shouldRevalidateLoader( } /** - * Idempotent utility to execute route.children() method to lazily load route + * Idempotent utility to execute patchRoutesOnMiss() to lazily load route * definitions and update the routes/routeManifest */ async function loadLazyRouteChildren( @@ -4510,7 +4557,7 @@ async function loadLazyRouteChildren( matches, patch: (routeId, children) => { if (!signal.aborted) { - patchRoutes( + patchRoutesImpl( routeId, children, routes, @@ -4531,10 +4578,10 @@ async function loadLazyRouteChildren( } } -function patchRoutes( +function patchRoutesImpl( routeId: string | null, children: AgnosticRouteObject[], - routes: AgnosticDataRouteObject[], + routesToUse: AgnosticDataRouteObject[], manifest: RouteManifest, mapRouteProperties: MapRoutePropertiesFunction ) { @@ -4559,10 +4606,10 @@ function patchRoutes( let dataChildren = convertRoutesToDataRoutes( children, mapRouteProperties, - ["patch", String(routes.length || "0")], + ["patch", String(routesToUse.length || "0")], manifest ); - routes.push(...dataChildren); + routesToUse.push(...dataChildren); } } diff --git a/packages/router/utils.ts b/packages/router/utils.ts index 2c5e30eabc..4e22db1566 100644 --- a/packages/router/utils.ts +++ b/packages/router/utils.ts @@ -1221,7 +1221,7 @@ export function getResolveToMatches< // https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329 if (v7_relativeSplatPath) { return pathMatches.map((match, idx) => - idx === matches.length - 1 ? match.pathname : match.pathnameBase + idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase ); }