Skip to content

Commit e766ab5

Browse files
johnpangalosJohn Pangalosbrophdawg11
authored
fix: Wrap route with location arg in location context (#9094)
* Wrap route with location arg in location context In order for the `useLocation` hook to work with the use of the modal pattern, routes with the locationArg prop are wrapped in a LocationContext. This is done conditionally in order to ensure performance in the default case doesn't change. * Update useLocation unit tests * Remove static markup from test for brevity * Back out formatting change to test Co-authored-by: John Pangalos <[email protected]> Co-authored-by: Matt Brophy <[email protected]>
1 parent e13e1f9 commit e766ab5

File tree

3 files changed

+75
-7
lines changed

3 files changed

+75
-7
lines changed

.changeset/hungry-vans-ring.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"react-router": patch
3+
"react-router-dom": patch
4+
"react-router-dom-v5-compat": patch
5+
"react-router-native": patch
6+
---
7+
8+
fix: update `useLocation` to return the scoped `Location` when inside a `<Routes location>` component

packages/react-router/__tests__/useLocation-test.tsx

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import * as React from "react";
22
import * as TestRenderer from "react-test-renderer";
33
import { MemoryRouter, Routes, Route, useLocation } from "react-router";
44

5-
function ShowPath() {
6-
let { pathname, search, hash } = useLocation();
7-
return <pre>{JSON.stringify({ pathname, search, hash })}</pre>;
5+
function ShowLocation() {
6+
let location = useLocation();
7+
return <pre>{JSON.stringify(location)}</pre>;
88
}
99

1010
describe("useLocation", () => {
@@ -14,16 +14,51 @@ describe("useLocation", () => {
1414
renderer = TestRenderer.create(
1515
<MemoryRouter initialEntries={["/home?the=search#the-hash"]}>
1616
<Routes>
17-
<Route path="/home" element={<ShowPath />} />
17+
<Route path="/home" element={<ShowLocation />} />
1818
</Routes>
1919
</MemoryRouter>
2020
);
2121
});
2222

2323
expect(renderer.toJSON()).toMatchInlineSnapshot(`
2424
<pre>
25-
{"pathname":"/home","search":"?the=search","hash":"#the-hash"}
25+
{"pathname":"/home","search":"?the=search","hash":"#the-hash","state":null,"key":"default"}
2626
</pre>
2727
`);
2828
});
29+
30+
it("returns the scoped location object when nested in <Routes location>", () => {
31+
let renderer: TestRenderer.ReactTestRenderer;
32+
TestRenderer.act(() => {
33+
renderer = TestRenderer.create(
34+
<MemoryRouter initialEntries={["/home?the=search#the-hash"]}>
35+
<App />
36+
</MemoryRouter>
37+
);
38+
});
39+
40+
function App() {
41+
return (
42+
<div>
43+
<Routes>
44+
<Route path="/home" element={<ShowLocation />} />
45+
</Routes>
46+
<Routes location="/scoped?scoped=search#scoped-hash">
47+
<Route path="/scoped" element={<ShowLocation />} />
48+
</Routes>
49+
</div>
50+
);
51+
}
52+
53+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
54+
<div>
55+
<pre>
56+
{"pathname":"/home","search":"?the=search","hash":"#the-hash","state":null,"key":"default"}
57+
</pre>
58+
<pre>
59+
{"pathname":"/scoped","search":"?scoped=search","hash":"#scoped-hash","state":null,"key":"default"}
60+
</pre>
61+
</div>
62+
`);
63+
});
2964
});

packages/react-router/lib/hooks.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import type {
88
PathPattern,
99
Router as RemixRouter,
1010
To,
11-
Action as NavigationType,
1211
} from "@remix-run/router";
1312
import {
13+
Action as NavigationType,
1414
invariant,
1515
isRouteErrorResponse,
1616
joinPaths,
@@ -425,7 +425,7 @@ export function useRoutes(
425425
);
426426
}
427427

428-
return _renderMatches(
428+
let renderedMatches = _renderMatches(
429429
matches &&
430430
matches.map((match) =>
431431
Object.assign({}, match, {
@@ -440,6 +440,31 @@ export function useRoutes(
440440
parentMatches,
441441
dataRouterStateContext || undefined
442442
);
443+
444+
// When a user passes in a `locationArg`, the associated routes need to
445+
// be wrapped in a new `LocationContext.Provider` in order for `useLocation`
446+
// to use the scoped location instead of the global location.
447+
if (locationArg) {
448+
return (
449+
<LocationContext.Provider
450+
value={{
451+
location: {
452+
pathname: "/",
453+
search: "",
454+
hash: "",
455+
state: null,
456+
key: "default",
457+
...location,
458+
},
459+
navigationType: NavigationType.Pop,
460+
}}
461+
>
462+
{renderedMatches}
463+
</LocationContext.Provider>
464+
);
465+
}
466+
467+
return renderedMatches;
443468
}
444469

445470
function DefaultErrorElement() {

0 commit comments

Comments
 (0)