Skip to content

Commit 0374930

Browse files
fix: relative navigation from index/pathless routes (#8954)
* fix: relative navigation from index routes (#8697) * fix: relative navigation from index routes * fix: tests * Handle nested pathless layout routes * cleanups * adjust approach to filter pathless routes * undo reordering of deps array Co-authored-by: Jaff Parker <[email protected]>
1 parent 121e85a commit 0374930

File tree

3 files changed

+160
-2
lines changed

3 files changed

+160
-2
lines changed

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- hyesungoh
2424
- IbraRouisDev
2525
- Isammoc
26+
- JaffParker
2627
- JakubDrozd
2728
- janpaepke
2829
- jimniels

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

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from "react";
22
import * as TestRenderer from "react-test-renderer";
3-
import { MemoryRouter, Navigate, Routes, Route } from "react-router";
3+
import { MemoryRouter, Navigate, Outlet, Routes, Route } from "react-router";
44

55
describe("<Navigate>", () => {
66
describe("with an absolute href", () => {
@@ -45,5 +45,152 @@ describe("<Navigate>", () => {
4545
</h1>
4646
`);
4747
});
48+
49+
it("handles upward navigation from an index routes", () => {
50+
let renderer: TestRenderer.ReactTestRenderer;
51+
TestRenderer.act(() => {
52+
renderer = TestRenderer.create(
53+
<MemoryRouter initialEntries={["/home"]}>
54+
<Routes>
55+
<Route path="home">
56+
<Route index element={<Navigate to="../about" />} />
57+
</Route>
58+
<Route path="about" element={<h1>About</h1>} />
59+
</Routes>
60+
</MemoryRouter>
61+
);
62+
});
63+
64+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
65+
<h1>
66+
About
67+
</h1>
68+
`);
69+
});
70+
71+
it("handles upward navigation from inside a pathless layout route", () => {
72+
let renderer: TestRenderer.ReactTestRenderer;
73+
TestRenderer.act(() => {
74+
renderer = TestRenderer.create(
75+
<MemoryRouter initialEntries={["/home"]}>
76+
<Routes>
77+
<Route element={<Outlet />}>
78+
<Route path="home" element={<Navigate to="../about" />} />
79+
</Route>
80+
<Route path="about" element={<h1>About</h1>} />
81+
</Routes>
82+
</MemoryRouter>
83+
);
84+
});
85+
86+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
87+
<h1>
88+
About
89+
</h1>
90+
`);
91+
});
92+
93+
it("handles upward navigation from inside multiple pathless layout routes + index route", () => {
94+
let renderer: TestRenderer.ReactTestRenderer;
95+
TestRenderer.act(() => {
96+
renderer = TestRenderer.create(
97+
<MemoryRouter initialEntries={["/home"]}>
98+
<Routes>
99+
<Route path="home">
100+
<Route element={<Outlet />}>
101+
<Route element={<Outlet />}>
102+
<Route element={<Outlet />}>
103+
<Route index element={<Navigate to="../about" />} />
104+
</Route>
105+
</Route>
106+
</Route>
107+
</Route>
108+
<Route path="about" element={<h1>About</h1>} />
109+
</Routes>
110+
</MemoryRouter>
111+
);
112+
});
113+
114+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
115+
<h1>
116+
About
117+
</h1>
118+
`);
119+
});
120+
121+
it("handles upward navigation from inside multiple pathless layout routes + path route", () => {
122+
let renderer: TestRenderer.ReactTestRenderer;
123+
TestRenderer.act(() => {
124+
renderer = TestRenderer.create(
125+
<MemoryRouter initialEntries={["/home/page"]}>
126+
<Routes>
127+
<Route path="home" element={<Outlet />}>
128+
<Route element={<Outlet />}>
129+
<Route element={<Outlet />}>
130+
<Route element={<Outlet />}>
131+
<Route
132+
path="page"
133+
element={<Navigate to="../../about" />}
134+
/>
135+
</Route>
136+
</Route>
137+
</Route>
138+
</Route>
139+
<Route path="about" element={<h1>About</h1>} />
140+
</Routes>
141+
</MemoryRouter>
142+
);
143+
});
144+
145+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
146+
<h1>
147+
About
148+
</h1>
149+
`);
150+
});
151+
152+
it("handles parent navigation from inside multiple pathless layout routes", () => {
153+
let renderer: TestRenderer.ReactTestRenderer;
154+
TestRenderer.act(() => {
155+
renderer = TestRenderer.create(
156+
<MemoryRouter initialEntries={["/home/page"]}>
157+
<Routes>
158+
<Route
159+
path="home"
160+
element={
161+
<>
162+
<h1>Home</h1>
163+
<Outlet />
164+
</>
165+
}
166+
>
167+
<Route element={<Outlet />}>
168+
<Route element={<Outlet />}>
169+
<Route element={<Outlet />}>
170+
<Route
171+
path="page"
172+
element={
173+
<>
174+
<h2>Page</h2>
175+
<Navigate to=".." />
176+
</>
177+
}
178+
/>
179+
</Route>
180+
</Route>
181+
</Route>
182+
</Route>
183+
<Route path="about" element={<h1>About</h1>} />
184+
</Routes>
185+
</MemoryRouter>
186+
);
187+
});
188+
189+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
190+
<h1>
191+
Home
192+
</h1>
193+
`);
194+
});
48195
});
49196
});

packages/react-router/lib/hooks.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,18 @@ export function useNavigate(): NavigateFunction {
155155
let { matches } = React.useContext(RouteContext);
156156
let { pathname: locationPathname } = useLocation();
157157

158+
// Ignore pathless matches (i.e., share the same pathname as their ancestor)
159+
let pathContributingMatches = matches.filter(
160+
(match, index) =>
161+
index === 0 || match.pathnameBase !== matches[index - 1].pathnameBase
162+
);
163+
164+
if (matches.length > 0 && matches[matches.length - 1].route.index) {
165+
pathContributingMatches.pop();
166+
}
167+
158168
let routePathnamesJson = JSON.stringify(
159-
matches.map((match) => match.pathnameBase)
169+
pathContributingMatches.map((match) => match.pathnameBase)
160170
);
161171

162172
let activeRef = React.useRef(false);

0 commit comments

Comments
 (0)