Skip to content

Commit 8f684eb

Browse files
fix: support basename and relative routing in loader/action redirects (#9447)
* fix: respect basename in loaders and actions redirects (#9418) Co-authored-by: Mikaël ANZANO <[email protected]> * fix: support basename and relative routes in redirects * ci: add tests for data memory router * add changeset * Bump bundle threshold * convert invariant to 404 for missing routeId * Bundle bump Co-authored-by: Mikaël Anzano <[email protected]> Co-authored-by: Mikaël ANZANO <[email protected]>
1 parent ec9bacf commit 8f684eb

File tree

5 files changed

+652
-61
lines changed

5 files changed

+652
-61
lines changed

.changeset/smart-ants-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Support basename and relative routing in loader/action redirects

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
},
108108
"filesize": {
109109
"packages/router/dist/router.js": {
110-
"none": "105 kB"
110+
"none": "106 kB"
111111
},
112112
"packages/react-router/dist/react-router.production.min.js": {
113113
"none": "12.5 kB"

packages/react-router/__tests__/data-memory-router-test.tsx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "@testing-library/react";
1010
import "@testing-library/jest-dom";
1111
import type { FormMethod, Router, RouterInit } from "@remix-run/router";
12+
import { joinPaths } from "@remix-run/router";
1213
import type { RouteObject } from "react-router";
1314
import {
1415
Await,
@@ -20,6 +21,7 @@ import {
2021
createMemoryRouter,
2122
createRoutesFromElements,
2223
defer,
24+
redirect,
2325
useActionData,
2426
useAsyncError,
2527
useAsyncValue,
@@ -158,6 +160,116 @@ describe("<DataMemoryRouter>", () => {
158160
`);
159161
});
160162

163+
it("prepends basename to loader/action redirects", async () => {
164+
let { container } = render(
165+
<DataMemoryRouter
166+
basename="/my/base/path"
167+
initialEntries={["/my/base/path"]}
168+
>
169+
<Route path="/" element={<Root />}>
170+
<Route path="thing" loader={() => redirect("/other")} />
171+
<Route path="other" element={<h1>Other</h1>} />
172+
</Route>
173+
</DataMemoryRouter>
174+
);
175+
176+
function Root() {
177+
return (
178+
<>
179+
<MemoryNavigate to="/thing">Link to thing</MemoryNavigate>
180+
<Outlet />
181+
</>
182+
);
183+
}
184+
185+
expect(getHtml(container)).toMatchInlineSnapshot(`
186+
"<div>
187+
<a
188+
href=\\"/my/base/path/thing\\"
189+
>
190+
Link to thing
191+
</a>
192+
</div>"
193+
`);
194+
195+
fireEvent.click(screen.getByText("Link to thing"));
196+
await waitFor(() => screen.getByText("Other"));
197+
expect(getHtml(container)).toMatchInlineSnapshot(`
198+
"<div>
199+
<a
200+
href=\\"/my/base/path/thing\\"
201+
>
202+
Link to thing
203+
</a>
204+
<h1>
205+
Other
206+
</h1>
207+
</div>"
208+
`);
209+
});
210+
211+
it("supports relative routing in loader/action redirects", async () => {
212+
let { container } = render(
213+
<DataMemoryRouter
214+
basename="/my/base/path"
215+
initialEntries={["/my/base/path"]}
216+
>
217+
<Route path="/" element={<Root />}>
218+
<Route path="parent" element={<Parent />}>
219+
<Route path="child" loader={() => redirect("../other")} />
220+
<Route path="other" element={<h2>Other</h2>} />
221+
</Route>
222+
</Route>
223+
</DataMemoryRouter>
224+
);
225+
226+
function Root() {
227+
return (
228+
<>
229+
<MemoryNavigate to="/parent/child">Link to child</MemoryNavigate>
230+
<Outlet />
231+
</>
232+
);
233+
}
234+
235+
function Parent() {
236+
return (
237+
<>
238+
<h1>Parent</h1>
239+
<Outlet />
240+
</>
241+
);
242+
}
243+
244+
expect(getHtml(container)).toMatchInlineSnapshot(`
245+
"<div>
246+
<a
247+
href=\\"/my/base/path/parent/child\\"
248+
>
249+
Link to child
250+
</a>
251+
</div>"
252+
`);
253+
254+
fireEvent.click(screen.getByText("Link to child"));
255+
await waitFor(() => screen.getByText("Parent"));
256+
expect(getHtml(container)).toMatchInlineSnapshot(`
257+
"<div>
258+
<a
259+
href=\\"/my/base/path/parent/child\\"
260+
>
261+
Link to child
262+
</a>
263+
<h1>
264+
Parent
265+
</h1>
266+
<h2>
267+
Other
268+
</h2>
269+
</div>"
270+
`);
271+
});
272+
161273
it("renders with hydration data", async () => {
162274
let { container } = render(
163275
<DataMemoryRouter
@@ -2817,6 +2929,11 @@ function MemoryNavigate({
28172929
}) {
28182930
let dataRouterContext = React.useContext(DataRouterContext);
28192931

2932+
let basename = dataRouterContext?.basename;
2933+
if (basename && basename !== "/") {
2934+
to = to === "/" ? basename : joinPaths([basename, to]);
2935+
}
2936+
28202937
let onClickHandler = React.useCallback(
28212938
async (event: React.MouseEvent) => {
28222939
event.preventDefault();

0 commit comments

Comments
 (0)