Skip to content

Commit 474b3b1

Browse files
authored
Fix hash issue for matchRoutes optimization (#12927)
1 parent 24e955a commit 474b3b1

File tree

3 files changed

+74
-16
lines changed

3 files changed

+74
-16
lines changed

.changeset/witty-birds-fetch.md

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 regression introduced in `7.1.4` via [#12800](https://github.com/remix-run/react-router/pull/12800) that caused issues navigating to hash routes inside splat routes for applications using Lazy Route Discovery (`patchRoutesOnNavigation`)

packages/react-router/__tests__/router/navigation-test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { JSDOM } from "jsdom";
2+
import { createBrowserRouter } from "../../lib/dom/lib";
13
import type { HydrationState } from "../../lib/router/router";
24
import { cleanup, setup } from "./utils/data-router-setup";
35
import { createFormData } from "./utils/utils";
@@ -60,6 +62,13 @@ function initializeTest(init?: {
6062
});
6163
}
6264

65+
function getWindowImpl(initialUrl: string, isHash = false): Window {
66+
// Need to use our own custom DOM in order to get a working history
67+
const dom = new JSDOM(`<!DOCTYPE html>`, { url: "http://localhost/" });
68+
dom.window.history.replaceState(null, "", (isHash ? "#" : "") + initialUrl);
69+
return dom.window as unknown as Window;
70+
}
71+
6372
describe("navigations", () => {
6473
afterEach(() => cleanup());
6574

@@ -431,6 +440,49 @@ describe("navigations", () => {
431440
});
432441
});
433442

443+
it("does not use fog of war partial matches for hash change only navigations", async () => {
444+
let router = createBrowserRouter(
445+
[
446+
{
447+
path: "/",
448+
children: [
449+
{
450+
path: "*",
451+
},
452+
],
453+
},
454+
],
455+
{
456+
window: getWindowImpl("/"),
457+
// This is what enables the partialMatches logic
458+
patchRoutesOnNavigation: () => {},
459+
}
460+
);
461+
expect(router.state.location).toMatchObject({
462+
pathname: "/",
463+
hash: "",
464+
});
465+
expect(router.state.matches).toMatchObject([{ route: { path: "/" } }]);
466+
await router.navigate("/foo");
467+
expect(router.state.location).toMatchObject({
468+
pathname: "/foo",
469+
hash: "",
470+
});
471+
expect(router.state.matches).toMatchObject([
472+
{ route: { path: "/" } },
473+
{ route: { path: "*" } },
474+
]);
475+
await router.navigate("/foo#bar");
476+
expect(router.state.location).toMatchObject({
477+
pathname: "/foo",
478+
hash: "#bar",
479+
});
480+
expect(router.state.matches).toMatchObject([
481+
{ route: { path: "/" } },
482+
{ route: { path: "*" } },
483+
]);
484+
});
485+
434486
it("redirects from loaders (throw)", async () => {
435487
let t = initializeTest();
436488

packages/react-router/lib/router/router.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,23 @@ export function createRouter(init: RouterInit): Router {
15331533
: matchRoutes(routesToUse, location, basename);
15341534
let flushSync = (opts && opts.flushSync) === true;
15351535

1536+
// Short circuit if it's only a hash change and not a revalidation or
1537+
// mutation submission.
1538+
//
1539+
// Ignore on initial page loads because since the initial hydration will always
1540+
// be "same hash". For example, on /page#hash and submit a <Form method="post">
1541+
// which will default to a navigation to /page
1542+
if (
1543+
matches &&
1544+
state.initialized &&
1545+
!isRevalidationRequired &&
1546+
isHashChangeOnly(state.location, location) &&
1547+
!(opts && opts.submission && isMutationMethod(opts.submission.formMethod))
1548+
) {
1549+
completeNavigation(location, { matches }, { flushSync });
1550+
return;
1551+
}
1552+
15361553
let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
15371554
if (fogOfWar.active && fogOfWar.matches) {
15381555
matches = fogOfWar.matches;
@@ -1557,22 +1574,6 @@ export function createRouter(init: RouterInit): Router {
15571574
return;
15581575
}
15591576

1560-
// Short circuit if it's only a hash change and not a revalidation or
1561-
// mutation submission.
1562-
//
1563-
// Ignore on initial page loads because since the initial hydration will always
1564-
// be "same hash". For example, on /page#hash and submit a <Form method="post">
1565-
// which will default to a navigation to /page
1566-
if (
1567-
state.initialized &&
1568-
!isRevalidationRequired &&
1569-
isHashChangeOnly(state.location, location) &&
1570-
!(opts && opts.submission && isMutationMethod(opts.submission.formMethod))
1571-
) {
1572-
completeNavigation(location, { matches }, { flushSync });
1573-
return;
1574-
}
1575-
15761577
// Create a controller/Request for this navigation
15771578
pendingNavigationController = new AbortController();
15781579
let request = createClientSideRequest(

0 commit comments

Comments
 (0)