Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- hyesungoh
- IbraRouisDev
- Isammoc
- JaffParker
- JakubDrozd
- janpaepke
- jimniels
Expand Down
149 changes: 148 additions & 1 deletion packages/react-router/__tests__/navigate-test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import * as TestRenderer from "react-test-renderer";
import { MemoryRouter, Navigate, Routes, Route } from "react-router";
import { MemoryRouter, Navigate, Outlet, Routes, Route } from "react-router";

describe("<Navigate>", () => {
describe("with an absolute href", () => {
Expand Down Expand Up @@ -45,5 +45,152 @@ describe("<Navigate>", () => {
</h1>
`);
});

it("handles upward navigation from an index routes", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home">
<Route index element={<Navigate to="../about" />} />
</Route>
<Route path="about" element={<h1>About</h1>} />
</Routes>
</MemoryRouter>
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
About
</h1>
`);
});

it("handles upward navigation from inside a pathless layout route", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route element={<Outlet />}>
<Route path="home" element={<Navigate to="../about" />} />
</Route>
<Route path="about" element={<h1>About</h1>} />
</Routes>
</MemoryRouter>
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
About
</h1>
`);
});

it("handles upward navigation from inside multiple pathless layout routes + index route", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home">
<Route element={<Outlet />}>
<Route element={<Outlet />}>
<Route element={<Outlet />}>
<Route index element={<Navigate to="../about" />} />
</Route>
</Route>
</Route>
</Route>
<Route path="about" element={<h1>About</h1>} />
</Routes>
</MemoryRouter>
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
About
</h1>
`);
});

it("handles upward navigation from inside multiple pathless layout routes + path route", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home/page"]}>
<Routes>
<Route path="home" element={<Outlet />}>
<Route element={<Outlet />}>
<Route element={<Outlet />}>
<Route element={<Outlet />}>
<Route
path="page"
element={<Navigate to="../../about" />}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanflorence Quick double check here that this double ../.. would be the expected relative path to the /about route?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<Route path="about" />
<Route path="home" element={<Outlet />}>
  <Route element={<Outlet />}>
    <Route element={<Outlet />}>
      <Route element={<Outlet />}>
        <Route
          path="page"
          element={<Navigate to="../../about" />}
        />

Should ignore all the pathless routes, so essentially we're talking about this:

<Route path="about" />
<Route path="home" element={<Outlet />}>
  <Route
    path="page"
    element={<Navigate to="../../about" />}
  />
  • .. goes up to /home route
  • .. x2 goes up to /
  • about goes to /about

So yeah, that sounds right!

/>
</Route>
</Route>
</Route>
</Route>
<Route path="about" element={<h1>About</h1>} />
</Routes>
</MemoryRouter>
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
About
</h1>
`);
});

it("handles parent navigation from inside multiple pathless layout routes", () => {
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home/page"]}>
<Routes>
<Route
path="home"
element={
<>
<h1>Home</h1>
<Outlet />
</>
}
>
<Route element={<Outlet />}>
<Route element={<Outlet />}>
<Route element={<Outlet />}>
<Route
path="page"
element={
<>
<h2>Page</h2>
<Navigate to=".." />
</>
}
/>
</Route>
</Route>
</Route>
</Route>
<Route path="about" element={<h1>About</h1>} />
</Routes>
</MemoryRouter>
);
});

expect(renderer.toJSON()).toMatchInlineSnapshot(`
<h1>
Home
</h1>
`);
});
});
});
12 changes: 11 additions & 1 deletion packages/react-router/lib/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,18 @@ export function useNavigate(): NavigateFunction {
let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();

// Ignore pathless matches (i.e., share the same pathname as their ancestor)
let pathContributingMatches = matches.filter(
(match, index) =>
index === 0 || match.pathnameBase !== matches[index - 1].pathnameBase
);

if (matches.length > 0 && matches[matches.length - 1].route.index) {
pathContributingMatches.pop();
}

let routePathnamesJson = JSON.stringify(
matches.map((match) => match.pathnameBase)
pathContributingMatches.map((match) => match.pathnameBase)
);

let activeRef = React.useRef(false);
Expand Down