Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix basename duplication in RouterProvider descendant routes #10492

Merged
merged 1 commit into from May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-basename-duplication.md
@@ -0,0 +1,5 @@
---
"react-router": patch
---

Fix `basename` duplication in descenant `<Routes>` inside a `<RouterProvider>`
175 changes: 175 additions & 0 deletions packages/react-router/__tests__/useNavigate-test.tsx
Expand Up @@ -2053,6 +2053,181 @@ describe("useNavigate", () => {
`);
});
});

describe("with a basename", () => {
describe("in a MemoryRouter", () => {
it("in a root route", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter basename="/base" initialEntries={["/base"]}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/path" element={<h1>Path</h1>} />
</Routes>
</MemoryRouter>
);
});

function Home() {
let navigate = useNavigate();
return <button onClick={() => navigate("/path")} />;
}

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<button
onClick={[Function]}
/>
`);

// @ts-expect-error
let button = renderer.root.findByType("button");
TestRenderer.act(() => button.props.onClick());

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
Path
</h1>
`);
});

it("in a descendant route", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter basename="/base" initialEntries={["/base"]}>
<Routes>
<Route
path="/*"
element={
<Routes>
<Route index element={<Home />} />
</Routes>
}
/>
<Route path="/path" element={<h1>Path</h1>} />
</Routes>
</MemoryRouter>
);
});

function Home() {
let navigate = useNavigate();
return <button onClick={() => navigate("/path")} />;
}

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<button
onClick={[Function]}
/>
`);

// @ts-expect-error
let button = renderer.root.findByType("button");
TestRenderer.act(() => button.props.onClick());

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
Path
</h1>
`);
});
});

describe("in a RouterProvider", () => {
it("in a root route", () => {
let router = createMemoryRouter(
[
{
path: "/",
Component: Home,
},
{ path: "/path", Component: () => <h1>Path</h1> },
],
{ basename: "/base", initialEntries: ["/base"] }
);

function Home() {
let navigate = useNavigate();
return <button onClick={() => navigate("/path")} />;
}

let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(<RouterProvider router={router} />);
});

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<button
onClick={[Function]}
/>
`);

// @ts-expect-error
let button = renderer.root.findByType("button");
TestRenderer.act(() => button.props.onClick());

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
Path
</h1>
`);
});

it("in a descendant route", () => {
let router = createMemoryRouter(
[
{
path: "/*",
Component() {
return (
<Routes>
<Route index element={<Home />} />
</Routes>
);
},
},
{ path: "/path", Component: () => <h1>Path</h1> },
],
{ basename: "/base", initialEntries: ["/base"] }
);

function Home() {
let navigate = useNavigate();
return <button onClick={() => navigate("/path")} />;
}

let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(<RouterProvider router={router} />);
});

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<button
onClick={[Function]}
/>
`);

// @ts-expect-error
let button = renderer.root.findByType("button");
TestRenderer.act(() => button.props.onClick());

// @ts-expect-error
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
Path
</h1>
`);
});
});
});
});

function UseNavigateButton({
Expand Down
19 changes: 14 additions & 5 deletions packages/react-router/lib/hooks.tsx
Expand Up @@ -188,6 +188,7 @@ function useNavigateUnstable(): NavigateFunction {
`useNavigate() may be used only in the context of a <Router> component.`
);

let dataRouterContext = React.useContext(DataRouterContext);
let { basename, navigator } = React.useContext(NavigationContext);
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
Expand Down Expand Up @@ -222,10 +223,12 @@ function useNavigateUnstable(): NavigateFunction {
);

// If we're operating within a basename, prepend it to the pathname prior
// to handing off to history. If this is a root navigation, then we
// navigate to the raw basename which allows the basename to have full
// control over the presence of a trailing slash on root links
if (basename !== "/") {
// to handing off to history (but only if we're not in a data router,
// otherwise it'll prepend the basename inside of the router).
// If this is a root navigation, then we navigate to the raw basename
// which allows the basename to have full control over the presence of a
// trailing slash on root links
if (dataRouterContext == null && basename !== "/") {
path.pathname =
path.pathname === "/"
? basename
Expand All @@ -238,7 +241,13 @@ function useNavigateUnstable(): NavigateFunction {
options
);
},
[basename, navigator, routePathnamesJson, locationPathname]
[
basename,
navigator,
routePathnamesJson,
locationPathname,
dataRouterContext,
]
);

return navigate;
Expand Down