diff --git a/.changeset/silver-coats-work.md b/.changeset/silver-coats-work.md new file mode 100644 index 0000000000..6fd814c3f7 --- /dev/null +++ b/.changeset/silver-coats-work.md @@ -0,0 +1,7 @@ +--- +"react-router-dom": patch +"react-router": patch +"@remix-run/router": patch +--- + +Rename `unstable_patchRoutesOnMiss` to `unstable_patchRoutesOnNavigation` because it will now be called on the first navigation to paths matching splat/param routes in case there exists a higher-scoring route match not yet discovered diff --git a/docs/routers/create-browser-router.md b/docs/routers/create-browser-router.md index 07cb871600..87bd13fca3 100644 --- a/docs/routers/create-browser-router.md +++ b/docs/routers/create-browser-router.md @@ -52,7 +52,7 @@ function createBrowserRouter( future?: FutureConfig; hydrationData?: HydrationState; unstable_dataStrategy?: unstable_DataStrategyFunction; - unstable_patchRoutesOnMiss?: unstable_PatchRoutesOnMissFunction; + unstable_patchRoutesOnNavigation?: unstable_PatchRoutesOnNavigationFunction; window?: Window; } ): RemixRouter; @@ -384,7 +384,7 @@ let router = createBrowserRouter(routes, { }); ``` -## `opts.unstable_patchRoutesOnMiss` +## `opts.unstable_patchRoutesOnNavigation` This API is marked "unstable" so it is subject to breaking API changes in minor releases @@ -394,12 +394,12 @@ To combat this, we introduced [`route.lazy`][route-lazy] in [v6.9.0][6-9-0] whic In some cases, even this doesn't go far enough. For very large applications, providing all route definitions up front can be prohibitively expensive. Additionally, it might not even be possible to provide all route definitions up front in certain Micro-Frontend or Module-Federation architectures. -This is where `unstable_patchRoutesOnMiss` comes in ([RFC][fog-of-war-rfc]). This API is for advanced use-cases where you are unable to provide the full route tree up-front and need a way to lazily "discover" portions of the route tree at runtime. This feature is often referred to as ["Fog of War"][fog-of-war] because similar to how video games expand the "world" as you move around - the router would be expanding its routing tree as the user navigated around the app - but would only ever end up loading portions of the tree that the user visited. +This is where `unstable_patchRoutesOnNavigation` comes in ([RFC][fog-of-war-rfc]). This API is for advanced use-cases where you are unable to provide the full route tree up-front and need a way to lazily "discover" portions of the route tree at runtime. This feature is often referred to as ["Fog of War"][fog-of-war] because similar to how video games expand the "world" as you move around - the router would be expanding its routing tree as the user navigated around the app - but would only ever end up loading portions of the tree that the user visited. ### Type Declaration ```ts -export interface unstable_PatchRoutesOnMissFunction { +export interface unstable_PatchRoutesOnNavigationFunction { (opts: { path: string; matches: RouteMatch[]; @@ -413,7 +413,7 @@ export interface unstable_PatchRoutesOnMissFunction { ### Overview -`unstable_patchRoutesOnMiss` will be called anytime React Router is unable to match a `path`. The arguments include the `path`, any partial `matches`, and a `patch` function you can call to patch new routes into the tree at a specific location. This method is executed during the `loading` portion of the navigation for `GET` requests and during the `submitting` portion of the navigation for non-`GET` requests. +`unstable_patchRoutesOnNavigation` will be called anytime React Router is unable to match a `path`. The arguments include the `path`, any partial `matches`, and a `patch` function you can call to patch new routes into the tree at a specific location. This method is executed during the `loading` portion of the navigation for `GET` requests and during the `submitting` portion of the navigation for non-`GET` requests. **Patching children into an existing route** @@ -427,7 +427,10 @@ const router = createBrowserRouter( }, ], { - async unstable_patchRoutesOnMiss({ path, patch }) { + async unstable_patchRoutesOnNavigation({ + path, + patch, + }) { if (path === "/a") { // Load/patch the `a` route as a child of the route with id `root` let route = await getARoute(); @@ -439,7 +442,7 @@ const router = createBrowserRouter( ); ``` -In the above example, if the user clicks a link to `/a`, React Router won't be able to match it initially and will call `patchRoutesOnMiss` with `/a` and a `matches` array containing the root route match. By calling `patch`, the `a` route will be added to the route tree and React Router will perform matching again. This time it will successfully match the `/a` path and the navigation will complete successfully. +In the above example, if the user clicks a link to `/a`, React Router won't be able to match it initially and will call `patchRoutesOnNavigation` with `/a` and a `matches` array containing the root route match. By calling `patch`, the `a` route will be added to the route tree and React Router will perform matching again. This time it will successfully match the `/a` path and the navigation will complete successfully. **Patching new root-level routes** @@ -455,7 +458,10 @@ const router = createBrowserRouter( }, ], { - async unstable_patchRoutesOnMiss({ path, patch }) { + async unstable_patchRoutesOnNavigation({ + path, + patch, + }) { if (path === "/root-sibling") { // Load/patch the `/root-sibling` route as a sibling of the root route let route = await getRootSiblingRoute(); @@ -480,7 +486,10 @@ let router = createBrowserRouter( }, ], { - async unstable_patchRoutesOnMiss({ path, patch }) { + async unstable_patchRoutesOnNavigation({ + path, + patch, + }) { if (path.startsWith("/dashboard")) { let children = await import("./dashboard"); patch(null, children); @@ -510,7 +519,7 @@ let router = createBrowserRouter( children: [ { // If we want to include /dashboard in the critical routes, we need to - // also include it's index route since patchRoutesOnMiss will not be + // also include it's index route since patchRoutesOnNavigation will not be // called on a navigation to `/dashboard` because it will have successfully // matched the `/dashboard` parent route index: true, @@ -535,7 +544,10 @@ let router = createBrowserRouter( }, ], { - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ + matches, + patch, + }) { let leafRoute = matches[matches.length - 1]?.route; if (leafRoute?.handle?.lazyChildren) { let children = diff --git a/packages/react-router-dom/__tests__/partial-hydration-test.tsx b/packages/react-router-dom/__tests__/partial-hydration-test.tsx index 918178a978..5a4eeeed34 100644 --- a/packages/react-router-dom/__tests__/partial-hydration-test.tsx +++ b/packages/react-router-dom/__tests__/partial-hydration-test.tsx @@ -31,7 +31,7 @@ describe("v7_partialHydration", () => { testPartialHydration(createMemoryRouter, ReactRouter_RouterProvider); // these tests only run for memory since we just need to set initialEntries - it("supports partial hydration w/patchRoutesOnMiss (leaf fallback)", async () => { + it("supports partial hydration w/patchRoutesOnNavigation (leaf fallback)", async () => { let parentDfd = createDeferred(); let childDfd = createDeferred(); let router = createMemoryRouter( @@ -69,7 +69,7 @@ describe("v7_partialHydration", () => { future: { v7_partialHydration: true, }, - unstable_patchRoutesOnMiss({ path, patch }) { + unstable_patchRoutesOnNavigation({ path, patch }) { if (path === "/parent/child") { patch("parent", [ { @@ -119,7 +119,7 @@ describe("v7_partialHydration", () => { `); }); - it("supports partial hydration w/patchRoutesOnMiss (root fallback)", async () => { + it("supports partial hydration w/patchRoutesOnNavigation (root fallback)", async () => { let parentDfd = createDeferred(); let childDfd = createDeferred(); let router = createMemoryRouter( @@ -157,7 +157,7 @@ describe("v7_partialHydration", () => { future: { v7_partialHydration: true, }, - unstable_patchRoutesOnMiss({ path, patch }) { + unstable_patchRoutesOnNavigation({ path, patch }) { if (path === "/parent/child") { patch("parent", [ { diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 54cb1a291c..6fba1e5265 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -16,7 +16,7 @@ import type { RouterProps, RouterProviderProps, To, - unstable_PatchRoutesOnMissFunction, + unstable_PatchRoutesOnNavigationFunction, } from "react-router"; import { Router, @@ -153,7 +153,7 @@ export type { To, UIMatch, unstable_HandlerResult, - unstable_PatchRoutesOnMissFunction, + unstable_PatchRoutesOnNavigationFunction, } from "react-router"; export { AbortedDeferredError, @@ -261,7 +261,7 @@ interface DOMRouterOpts { future?: Partial>; hydrationData?: HydrationState; unstable_dataStrategy?: unstable_DataStrategyFunction; - unstable_patchRoutesOnMiss?: unstable_PatchRoutesOnMissFunction; + unstable_patchRoutesOnNavigation?: unstable_PatchRoutesOnNavigationFunction; window?: Window; } @@ -280,7 +280,7 @@ export function createBrowserRouter( routes, mapRouteProperties, unstable_dataStrategy: opts?.unstable_dataStrategy, - unstable_patchRoutesOnMiss: opts?.unstable_patchRoutesOnMiss, + unstable_patchRoutesOnNavigation: opts?.unstable_patchRoutesOnNavigation, window: opts?.window, }).initialize(); } @@ -300,7 +300,7 @@ export function createHashRouter( routes, mapRouteProperties, unstable_dataStrategy: opts?.unstable_dataStrategy, - unstable_patchRoutesOnMiss: opts?.unstable_patchRoutesOnMiss, + unstable_patchRoutesOnNavigation: opts?.unstable_patchRoutesOnNavigation, window: opts?.window, }).initialize(); } diff --git a/packages/react-router/index.ts b/packages/react-router/index.ts index 5368cc6369..6ddef0c1b0 100644 --- a/packages/react-router/index.ts +++ b/packages/react-router/index.ts @@ -32,7 +32,7 @@ import type { To, UIMatch, unstable_HandlerResult, - unstable_AgnosticPatchRoutesOnMissFunction, + unstable_AgnosticPatchRoutesOnNavigationFunction, } from "@remix-run/router"; import { AbortedDeferredError, @@ -291,8 +291,8 @@ function mapRouteProperties(route: RouteObject) { return updates; } -export interface unstable_PatchRoutesOnMissFunction - extends unstable_AgnosticPatchRoutesOnMissFunction {} +export interface unstable_PatchRoutesOnNavigationFunction + extends unstable_AgnosticPatchRoutesOnNavigationFunction {} export function createMemoryRouter( routes: RouteObject[], @@ -303,7 +303,7 @@ export function createMemoryRouter( initialEntries?: InitialEntry[]; initialIndex?: number; unstable_dataStrategy?: unstable_DataStrategyFunction; - unstable_patchRoutesOnMiss?: unstable_PatchRoutesOnMissFunction; + unstable_patchRoutesOnNavigation?: unstable_PatchRoutesOnNavigationFunction; } ): RemixRouter { return createRouter({ @@ -320,7 +320,7 @@ export function createMemoryRouter( routes, mapRouteProperties, unstable_dataStrategy: opts?.unstable_dataStrategy, - unstable_patchRoutesOnMiss: opts?.unstable_patchRoutesOnMiss, + unstable_patchRoutesOnNavigation: opts?.unstable_patchRoutesOnNavigation, }).initialize(); } diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index ccce084a93..486b7fbcaf 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -698,7 +698,7 @@ export function _renderMatches( dataRouterState.matches.length > 0 ) { // Don't bail if we're initializing with partial hydration and we have - // router matches. That means we're actively running `patchRoutesOnMiss` + // router matches. That means we're actively running `patchRoutesOnNavigation` // so we should render down the partial matches to the appropriate // `HydrateFallback`. We only do this if `parentMatches` is empty so it // only impacts the root matches for `RouterProvider` and no descendant diff --git a/packages/router/__tests__/lazy-discovery-test.ts b/packages/router/__tests__/lazy-discovery-test.ts index 808f498873..3600b3af12 100644 --- a/packages/router/__tests__/lazy-discovery-test.ts +++ b/packages/router/__tests__/lazy-discovery-test.ts @@ -33,7 +33,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { loader: () => loaderDfd.promise, }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -89,7 +89,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ patch, matches }) { + async unstable_patchRoutesOnNavigation({ patch, matches }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -145,7 +145,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { loader: () => loaderDfd.promise, }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -219,7 +219,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ patch, matches }) { + async unstable_patchRoutesOnNavigation({ patch, matches }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -283,7 +283,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ path, matches, patch }) { + async unstable_patchRoutesOnNavigation({ path, matches, patch }) { let routeId = last(matches).route.id; calls.push([path, routeId]); patch("a", await aDfd.promise); @@ -338,7 +338,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ path, matches, patch }) { + async unstable_patchRoutesOnNavigation({ path, matches, patch }) { let routeId = last(matches).route.id; if (!path) { return; @@ -441,7 +441,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { }, }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { let leafRoute = last(matches).route; patch(leafRoute.id, await leafRoute.handle.loadChildren?.()); }, @@ -471,7 +471,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -519,7 +519,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/:slug", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { await tick(); patch(null, [ { @@ -547,7 +547,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/product/:slug", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { await tick(); patch(null, [ { @@ -581,7 +581,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { ], }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { await tick(); patch("product", [ { @@ -612,7 +612,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/:slug", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); }, }); @@ -638,7 +638,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -668,7 +668,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/splat/*", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); patch(null, [ { @@ -702,7 +702,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { ], }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { await tick(); patch("product", [ { @@ -737,7 +737,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -755,7 +755,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { expect(router.state.matches.map((m) => m.route.id)).toEqual(["splat"]); }); - it("recurses unstable_patchRoutesOnMiss until a match is found", async () => { + it("recurses unstable_patchRoutesOnNavigation until a match is found", async () => { let count = 0; router = createRouter({ history: createMemoryHistory(), @@ -768,7 +768,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); count++; if (last(matches).route.id === "a") { @@ -816,7 +816,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { loader: () => loaderDfd.promise, }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -872,7 +872,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { loader: () => loaderDfd.promise, }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -927,7 +927,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "*", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch(null, children); }, @@ -965,7 +965,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { splat: "SPLAT 1", }, }, - async unstable_patchRoutesOnMiss() { + async unstable_patchRoutesOnNavigation() { throw new Error("Should not be called"); }, }); @@ -992,7 +992,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/parent", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { patch(null, await childrenDfd.promise); }, }); @@ -1043,7 +1043,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/:param", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { // We matched for the param but we want to patch in under root expect(matches.length).toBe(1); expect(matches[0].route.id).toBe("param"); @@ -1098,7 +1098,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "*", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { // We matched for the splat but we want to patch in at the top expect(matches.length).toBe(1); expect(matches[0].route.id).toBe("splat"); @@ -1148,7 +1148,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "/nope", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { expect(matches.length).toBe(0); let children = await childrenDfd.promise; patch(null, children); @@ -1199,7 +1199,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "parent", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -1272,7 +1272,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: ":param", }, ], - async unstable_patchRoutesOnMiss() { + async unstable_patchRoutesOnNavigation() { count++; await tick(); // Nothing to patch - there is no better static route in this case @@ -1308,7 +1308,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "static", }, ], - async unstable_patchRoutesOnMiss() { + async unstable_patchRoutesOnNavigation() { count++; }, }); @@ -1356,7 +1356,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: ":param", }, ], - async unstable_patchRoutesOnMiss() { + async unstable_patchRoutesOnNavigation() { count++; // Nothing to patch - there is no better static route in this case }, @@ -1373,17 +1373,17 @@ describe("Lazy Route Discovery (Fog of War)", () => { expect(router.state.location.pathname).toBe("/"); } - // Don't call patchRoutesOnMiss since this is the first item in the queue + // Don't call patchRoutesOnNavigation since this is the first item in the queue await router.navigate(`/path-1`); expect(count).toBe(1000); expect(router.state.location.pathname).toBe(`/path-1`); - // Call patchRoutesOnMiss and evict the first item + // Call patchRoutesOnNavigation and evict the first item await router.navigate(`/path-1001`); expect(count).toBe(1001); expect(router.state.location.pathname).toBe(`/path-1001`); - // Call patchRoutesOnMiss since this item was evicted + // Call patchRoutesOnNavigation since this item was evicted await router.navigate(`/path-1`); expect(count).toBe(1002); expect(router.state.location.pathname).toBe(`/path-1`); @@ -1404,7 +1404,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "parent", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -1459,7 +1459,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "parent", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -1516,7 +1516,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -1569,7 +1569,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -1622,7 +1622,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -1674,7 +1674,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -1731,7 +1731,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -1788,7 +1788,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -1832,7 +1832,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { ]); }); - it("handles errors thrown from patchRoutesOnMiss() (GET navigation)", async () => { + it("handles errors thrown from patchRoutesOnNavigation() (GET navigation)", async () => { let shouldThrow = true; router = createRouter({ history: createMemoryHistory(), @@ -1846,7 +1846,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { await tick(); if (shouldThrow) { shouldThrow = false; @@ -1874,7 +1874,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { 400, "Bad Request", new Error( - 'Unable to match URL "/a/b" - the `unstable_patchRoutesOnMiss()` ' + + 'Unable to match URL "/a/b" - the `unstable_patchRoutesOnNavigation()` ' + "function threw the following error:\nError: broke!" ), true @@ -1904,7 +1904,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { expect(router.state.matches.map((m) => m.route.id)).toEqual(["a", "b"]); }); - it("handles errors thrown from patchRoutesOnMiss() (POST navigation)", async () => { + it("handles errors thrown from patchRoutesOnNavigation() (POST navigation)", async () => { let shouldThrow = true; router = createRouter({ history: createMemoryHistory(), @@ -1918,7 +1918,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { await tick(); if (shouldThrow) { shouldThrow = false; @@ -1949,7 +1949,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { 400, "Bad Request", new Error( - 'Unable to match URL "/a/b" - the `unstable_patchRoutesOnMiss()` ' + + 'Unable to match URL "/a/b" - the `unstable_patchRoutesOnNavigation()` ' + "function threw the following error:\nError: broke!" ), true @@ -1982,7 +1982,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { expect(router.state.matches.map((m) => m.route.id)).toEqual(["a", "b"]); }); - it("bubbles errors thrown from patchRoutesOnMiss() during hydration", async () => { + it("bubbles errors thrown from patchRoutesOnNavigation() during hydration", async () => { router = createRouter({ history: createMemoryHistory({ initialEntries: ["/parent/child/grandchild"], @@ -2000,7 +2000,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { ], }, ], - async unstable_patchRoutesOnMiss() { + async unstable_patchRoutesOnNavigation() { await tick(); throw new Error("broke!"); }, @@ -2024,7 +2024,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { "Bad Request", new Error( 'Unable to match URL "/parent/child/grandchild" - the ' + - "`unstable_patchRoutesOnMiss()` function threw the following " + + "`unstable_patchRoutesOnNavigation()` function threw the following " + "error:\nError: broke!" ), true @@ -2037,7 +2037,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { ]); }); - it("bubbles errors thrown from patchRoutesOnMiss() during hydration (w/v7_partialHydration)", async () => { + it("bubbles errors thrown from patchRoutesOnNavigation() during hydration (w/v7_partialHydration)", async () => { router = createRouter({ history: createMemoryHistory({ initialEntries: ["/parent/child/grandchild"], @@ -2055,7 +2055,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { ], }, ], - async unstable_patchRoutesOnMiss() { + async unstable_patchRoutesOnNavigation() { await tick(); throw new Error("broke!"); }, @@ -2085,7 +2085,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { "Bad Request", new Error( 'Unable to match URL "/parent/child/grandchild" - the ' + - "`unstable_patchRoutesOnMiss()` function threw the following " + + "`unstable_patchRoutesOnNavigation()` function threw the following " + "error:\nError: broke!" ), true @@ -2115,7 +2115,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "parent", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -2153,7 +2153,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ @@ -2200,7 +2200,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "parent", }, ], - async unstable_patchRoutesOnMiss({ patch }) { + async unstable_patchRoutesOnNavigation({ patch }) { let children = await childrenDfd.promise; patch("parent", children); }, @@ -2241,7 +2241,7 @@ describe("Lazy Route Discovery (Fog of War)", () => { path: "a", }, ], - async unstable_patchRoutesOnMiss({ matches, patch }) { + async unstable_patchRoutesOnNavigation({ matches, patch }) { await tick(); if (last(matches).route.id === "a") { patch("a", [ diff --git a/packages/router/index.ts b/packages/router/index.ts index 34f956b20f..5e6dcfe3dc 100644 --- a/packages/router/index.ts +++ b/packages/router/index.ts @@ -23,7 +23,7 @@ export type { LoaderFunctionArgs, ParamParseKey, Params, - AgnosticPatchRoutesOnMissFunction as unstable_AgnosticPatchRoutesOnMissFunction, + AgnosticPatchRoutesOnNavigationFunction as unstable_AgnosticPatchRoutesOnNavigationFunction, PathMatch, PathParam, PathPattern, diff --git a/packages/router/router.ts b/packages/router/router.ts index dde964f58d..da4a43db72 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -35,7 +35,7 @@ import type { UIMatch, V7_FormMethod, V7_MutationFormMethod, - AgnosticPatchRoutesOnMissFunction, + AgnosticPatchRoutesOnNavigationFunction, DataWithResponseInit, } from "./utils"; import { @@ -391,7 +391,7 @@ export interface RouterInit { future?: Partial; hydrationData?: HydrationState; window?: Window; - unstable_patchRoutesOnMiss?: AgnosticPatchRoutesOnMissFunction; + unstable_patchRoutesOnNavigation?: AgnosticPatchRoutesOnNavigationFunction; unstable_dataStrategy?: DataStrategyFunction; } @@ -798,7 +798,7 @@ export function createRouter(init: RouterInit): Router { let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined; let basename = init.basename || "/"; let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy; - let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss; + let patchRoutesOnNavigationImpl = init.unstable_patchRoutesOnNavigation; // Config driven behavior flags let future: FutureConfig = { @@ -835,7 +835,7 @@ export function createRouter(init: RouterInit): Router { let initialMatches = matchRoutes(dataRoutes, init.history.location, basename); let initialErrors: RouteData | null = null; - if (initialMatches == null && !patchRoutesOnMissImpl) { + if (initialMatches == null && !patchRoutesOnNavigationImpl) { // If we do not match a user-provided-route, fall back to the root // to allow the error boundary to take over let error = getInternalRouterError(404, { @@ -846,7 +846,7 @@ export function createRouter(init: RouterInit): Router { initialErrors = { [route.id]: error }; } - // In SPA apps, if the user provided a patchRoutesOnMiss implementation and + // In SPA apps, if the user provided a patchRoutesOnNavigation 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. // In SSR apps (with `hydrationData`), we expect that the server will send @@ -869,7 +869,7 @@ export function createRouter(init: RouterInit): Router { initialMatches = []; // If partial hydration and fog of war is enabled, we will be running - // `patchRoutesOnMiss` during hydration so include any partial matches as + // `patchRoutesOnNavigation` during hydration so include any partial matches as // the initial matches so we can properly render `HydrateFallback`'s if (future.v7_partialHydration) { let fogOfWar = checkFogOfWar( @@ -1024,11 +1024,11 @@ export function createRouter(init: RouterInit): Router { // we don't need to update UI state if they change let blockerFunctions = new Map(); - // Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so + // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so // that we only kick them off once for a given combo let pendingPatchRoutes = new Map< string, - ReturnType + ReturnType >(); // Flag to ignore the next history update, so we can revert the URL change on @@ -3186,7 +3186,7 @@ export function createRouter(init: RouterInit): Router { routesToUse: AgnosticDataRouteObject[], pathname: string ): { active: boolean; matches: AgnosticDataRouteMatch[] | null } { - if (patchRoutesOnMissImpl) { + if (patchRoutesOnNavigationImpl) { // Don't bother re-calling patchRouteOnMiss for a path we've already // processed. the last execution would have patched the route tree // accordingly so `matches` here are already accurate. @@ -3207,7 +3207,7 @@ export function createRouter(init: RouterInit): Router { if (Object.keys(matches[0].params).length > 0) { // If we matched a dynamic param or a splat, it might only be because // we haven't yet discovered other routes that would match with a - // higher score. Call patchRoutesOnMiss just to be sure + // higher score. Call patchRoutesOnNavigation just to be sure let partialMatches = matchRoutesImpl( routesToUse, pathname, @@ -3248,7 +3248,7 @@ export function createRouter(init: RouterInit): Router { let routesToUse = inFlightDataRoutes || dataRoutes; try { await loadLazyRouteChildren( - patchRoutesOnMissImpl!, + patchRoutesOnNavigationImpl!, pathname, partialMatches, routesToUse, @@ -4544,24 +4544,27 @@ function shouldRevalidateLoader( } /** - * Idempotent utility to execute patchRoutesOnMiss() to lazily load route + * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route * definitions and update the routes/routeManifest */ async function loadLazyRouteChildren( - patchRoutesOnMissImpl: AgnosticPatchRoutesOnMissFunction, + patchRoutesOnNavigationImpl: AgnosticPatchRoutesOnNavigationFunction, path: string, matches: AgnosticDataRouteMatch[], routes: AgnosticDataRouteObject[], manifest: RouteManifest, mapRouteProperties: MapRoutePropertiesFunction, - pendingRouteChildren: Map>, + pendingRouteChildren: Map< + string, + ReturnType + >, signal: AbortSignal ) { let key = [path, ...matches.map((m) => m.route.id)].join("-"); try { let pending = pendingRouteChildren.get(key); if (!pending) { - pending = patchRoutesOnMissImpl({ + pending = patchRoutesOnNavigationImpl({ path, matches, patch: (routeId, children) => { @@ -5400,7 +5403,7 @@ function getInternalRouterError( statusText = "Bad Request"; if (type === "route-discovery") { errorMessage = - `Unable to match URL "${pathname}" - the \`unstable_patchRoutesOnMiss()\` ` + + `Unable to match URL "${pathname}" - the \`unstable_patchRoutesOnNavigation()\` ` + `function threw the following error:\n${message}`; } else if (method && pathname && routeId) { errorMessage = diff --git a/packages/router/utils.ts b/packages/router/utils.ts index 2383ea829d..8b9df61f10 100644 --- a/packages/router/utils.ts +++ b/packages/router/utils.ts @@ -254,7 +254,7 @@ export interface DataStrategyFunction { (args: DataStrategyFunctionArgs): Promise; } -export interface AgnosticPatchRoutesOnMissFunction< +export interface AgnosticPatchRoutesOnNavigationFunction< M extends AgnosticRouteMatch = AgnosticRouteMatch > { (opts: {