Skip to content

Commit

Permalink
Add useNavigationType() hook
Browse files Browse the repository at this point in the history
This exposes the current history "action". It has been renamed to
"navigation type" here because we have future plans for using the word
"action" when it comes to data mutations.
  • Loading branch information
mjackson committed Oct 29, 2021
1 parent 67f345b commit 067b98b
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 153 deletions.
16 changes: 16 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ There are a few low-level APIs that we use internally that may also prove useful

- [`useResolvedPath`](#useresolvedpath) - resolves a relative path against the current [location](#location)
- [`useHref`](#usehref) - resolves a relative path suitable for use as a `<a href>`
- [`useLocation`](#uselocation) and [`useNavigationType`](#usenavigationtype) - these describe the current [location](#location) and how we got there
- [`useLinkClickHandler`](#uselinkclickhandler) - returns an event handler to for navigation when building a custom `<Link>` in `react-router-dom`
- [`useLinkPressHandler`](#uselinkpresshandler) - returns an event handler to for navigation when building a custom `<Link>` in `react-router-native`
- [`resolvePath`](#resolvepath) - resolves a relative path against a given URL pathname
Expand Down Expand Up @@ -1057,6 +1058,21 @@ function App() {
}
```

### `useNavigationType`

<details>
<summary>Type declaration</summary>

```tsx
declare function useNavigationType(): NavigationType;

type NavigationType = "POP" | "PUSH" | "REPLACE";
```

</details>

This hook returns the current type of navigation or how the user came to the current page; either via a pop, push, or replace action on the history stack.

### `useMatch`

<details>
Expand Down
270 changes: 126 additions & 144 deletions packages/react-router-dom/__tests__/link-push-test.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,18 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { act } from "react-dom/test-utils";
import { Router, Routes, Route, Link } from "react-router-dom";
import type { History } from "history";

function click(anchor: HTMLAnchorElement, eventInit?: MouseEventInit): void {
anchor.dispatchEvent(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
...eventInit
})
);
}

function createHref({ pathname = "/", search = "", hash = "" }): string {
return pathname + search + hash;
}

function createMockHistory({ pathname = "/", search = "", hash = "" }) {
let location: Partial<History["location"]> = { pathname, search, hash };
return {
action: "POP",
location,
createHref,
push() {},
replace() {},
go() {},
back() {},
forward() {},
listen() {
return () => {};
}
} as Omit<History, "block">;
import * as TestRenderer from "react-test-renderer";
import {
MemoryRouter,
Routes,
Route,
Link,
useNavigationType
} from "react-router-dom";

function ShowNavigationType() {
return <p>{useNavigationType()}</p>;
}

describe("Link push and replace", () => {
let node: HTMLDivElement;
beforeEach(() => {
node = document.createElement("div");
document.body.appendChild(node);
});

afterEach(() => {
document.body.removeChild(node);
node = null!;
});

describe("to a different pathname, when it is clicked", () => {
it("performs a push", () => {
function Home() {
Expand All @@ -59,133 +24,143 @@ describe("Link push and replace", () => {
);
}

let history = createMockHistory({ pathname: "/home" });
let spy = jest.spyOn(history, "push");

act(() => {
ReactDOM.render(
<Router
action={history.action}
location={history.location}
navigator={history}
>
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home" element={<Home />} />
<Route path="about" element={<ShowNavigationType />} />
</Routes>
</Router>,
node
</MemoryRouter>
);
});

let anchor = node.querySelector("a");
expect(anchor).not.toBeNull();
let anchor = renderer.root.findByType("a");

act(() => {
click(anchor);
TestRenderer.act(() => {
anchor.props.onClick(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
})
);
});

expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
pathname: "/about",
search: "",
hash: ""
}),
undefined
);
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<p>
PUSH
</p>
`);
});
});

describe("to a different search string, when it is clicked", () => {
it("performs a push (with the existing pathname)", () => {
it("performs a push with the existing pathname", () => {
function Home() {
return (
<div>
<h1>Home</h1>
<Link to="?name=michael">Michael</Link>
<ShowNavigationType />
</div>
);
}

let history = createMockHistory({ pathname: "/home" });
let spy = jest.spyOn(history, "push");

act(() => {
ReactDOM.render(
<Router
action={history.action}
location={history.location}
navigator={history}
>
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home" element={<Home />} />
</Routes>
</Router>,
node
</MemoryRouter>
);
});

let anchor = node.querySelector("a");
expect(anchor).not.toBeNull();
let anchor = renderer.root.findByType("a");

act(() => {
click(anchor);
TestRenderer.act(() => {
anchor.props.onClick(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
})
);
});

expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
pathname: "/home",
search: "?name=michael",
hash: ""
}),
undefined
);
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<div>
<h1>
Home
</h1>
<a
href="/home?name=michael"
onClick={[Function]}
>
Michael
</a>
<p>
PUSH
</p>
</div>
`);
});
});

describe("to a different hash, when it is clicked", () => {
it("performs a push (with the existing pathname)", () => {
it("performs a push with the existing pathname", () => {
function Home() {
return (
<div>
<h1>Home</h1>
<Link to="#bio">Bio</Link>
<ShowNavigationType />
</div>
);
}

let history = createMockHistory({ pathname: "/home" });
let spy = jest.spyOn(history, "push");

act(() => {
ReactDOM.render(
<Router
action={history.action}
location={history.location}
navigator={history}
>
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home" element={<Home />} />
</Routes>
</Router>,
node
</MemoryRouter>
);
});

let anchor = node.querySelector("a");
expect(anchor).not.toBeNull();
let anchor = renderer.root.findByType("a");

act(() => {
click(anchor);
TestRenderer.act(() => {
anchor.props.onClick(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
})
);
});

expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
pathname: "/home",
search: "",
hash: "#bio"
}),
undefined
);
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<div>
<h1>
Home
</h1>
<a
href="/home#bio"
onClick={[Function]}
>
Bio
</a>
<p>
PUSH
</p>
</div>
`);
});
});

Expand All @@ -196,6 +171,7 @@ describe("Link push and replace", () => {
<div>
<h1>Home</h1>
<Link to=".">Home</Link>
<ShowNavigationType />
</div>
);
}
Expand All @@ -204,40 +180,46 @@ describe("Link push and replace", () => {
return <h1>About</h1>;
}

let history = createMockHistory({ pathname: "/home" });
let spy = jest.spyOn(history, "replace");

act(() => {
ReactDOM.render(
<Router
action={history.action}
location={history.location}
navigator={history}
>
let renderer: TestRenderer.ReactTestRenderer;
TestRenderer.act(() => {
renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="home" element={<Home />} />
<Route path="about" element={<About />} />
</Routes>
</Router>,
node
</MemoryRouter>
);
});

let anchor = node.querySelector("a");
expect(anchor).not.toBeNull();
let anchor = renderer.root.findByType("a");

act(() => {
click(anchor);
TestRenderer.act(() => {
anchor.props.onClick(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
})
);
});

expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
pathname: "/home",
search: "",
hash: ""
}),
undefined
);
expect(renderer.toJSON()).toMatchInlineSnapshot(`
<div>
<h1>
Home
</h1>
<a
href="/home"
onClick={[Function]}
>
Home
</a>
<p>
REPLACE
</p>
</div>
`);
});
});
});
Loading

0 comments on commit 067b98b

Please sign in to comment.