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

Add a link API that navigates without duplicating paths #617

Closed
wants to merge 1 commit into from
Closed
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
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## HEAD
> Nov 02, 2018

- Add `history.link` which navigates and prevents same paths in the history stack

## [v4.6.3]
> Jun 20, 2017

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ unlisten();
- `history.goBack()`
- `history.goForward()`
- `history.canGo(n)` (only in `createMemoryHistory`)
- `history.link(path, [state])`

When using `push` or `replace` you can either specify both the URL path and state as separate arguments or include everything in a single location-like object as the first argument.
When requiring an action to behave like a link (not pushing duplicate paths to the stack) you can use the `link` method.

1. A URL path _or_
2. A location-like object with `{ pathname, search, hash, state }`
Expand Down
10 changes: 10 additions & 0 deletions modules/LocationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ export function createLocation(path, state, key, currentLocation) {
return location;
}

export function shouldReplace(location, newPath, newState) {
const nextLocation = createLocation(newPath, newState, null, location);

return (
location.pathname === nextLocation.pathname &&
location.search === nextLocation.search &&
location.hash === nextLocation.hash
);
}

export function locationsAreEqual(a, b) {
return (
a.pathname === b.pathname &&
Expand Down
6 changes: 6 additions & 0 deletions modules/__tests__/BrowserHistory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ describe("a browser history", () => {
});
});

describe("navigate with link to the same path", () => {
it("does not add a new location onto the stack, unless the state has change", done => {
TestSequences.LinkSamePath(history, done);
});
});

describe("location created by encoded and unencoded pathname", () => {
it("produces the same location.pathname", done => {
TestSequences.LocationPathnameAlwaysDecoded(history, done);
Expand Down
6 changes: 6 additions & 0 deletions modules/__tests__/HashHistory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ describe("a hash history", () => {
});
});

describe("navigate with link to the same path", () => {
it("calls change listeners with the same location and emits a warning", done => {
TestSequences.LinkSamePathWarning(history, done);
});
});

describe("location created by encoded and unencoded pathname", () => {
it("produces the same location.pathname", done => {
TestSequences.LocationPathnameAlwaysDecoded(history, done);
Expand Down
6 changes: 6 additions & 0 deletions modules/__tests__/MemoryHistory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ describe("a memory history", () => {
});
});

describe("navigate with link to the same path", () => {
it("does not add a new location onto the stack, unless the state has change", done => {
TestSequences.LinkSamePath(history, done);
});
});

describe("location created by encoded and unencoded pathname", () => {
it("produces the same location.pathname", done => {
TestSequences.LocationPathnameAlwaysDecoded(history, done);
Expand Down
63 changes: 63 additions & 0 deletions modules/__tests__/TestSequences/LinkSamePath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import expect from "expect";
import execSteps from "./execSteps";

export default function(history, done) {
const steps = [
location => {
expect(location).toMatchObject({
pathname: "/"
});

history.link("/home");
},
(location, action) => {
expect(action).toBe("PUSH");
expect(location).toMatchObject({
pathname: "/home"
});

history.link("/home");
},
(location, action) => {
expect(action).toBe("REPLACE");
expect(location).toMatchObject({
pathname: "/home"
});

history.goBack();
},
(location, action) => {
expect(action).toBe("POP");
expect(location).toMatchObject({
pathname: "/"
});

history.link("/home");
},
(location, action) => {
expect(action).toBe("PUSH");
expect(location).toMatchObject({
pathname: "/home"
});

history.link("/home", {the: "state"});
},
(location, action) => {
expect(action).toBe("REPLACE");
expect(location).toMatchObject({
pathname: "/home",
state: {the: "state"}
});

history.goBack();
},
(location, action) => {
expect(action).toBe("POP");
expect(location).toMatchObject({
pathname: "/"
});
}
];

execSteps(steps, history, done);
}
54 changes: 54 additions & 0 deletions modules/__tests__/TestSequences/LinkSamePathWarning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import expect from "expect";
import execSteps from "./execSteps";

export default function(history, done) {
let prevLocation;

const steps = [
location => {
expect(location).toMatchObject({
pathname: "/"
});

history.link("/home");
},
(location, action) => {
expect(action).toBe("PUSH");
expect(location).toMatchObject({
pathname: "/home"
});

prevLocation = location;

history.link("/home");
},
(location, action) => {
expect(action).toBe("PUSH");
expect(location).toMatchObject({
pathname: "/home"
});

// We should get the SAME location object. Nothing
// new was added to the history stack.
expect(location).toBe(prevLocation);

// We should see a warning message.
expect(warningMessage).toMatch(
"Hash history cannot PUSH the same path; a new entry will not be added to the history stack"
);
}
];

let consoleWarn = console.warn; // eslint-disable-line no-console
let warningMessage;

// eslint-disable-next-line no-console
console.warn = message => {
warningMessage = message;
};

execSteps(steps, history, (...args) => {
console.warn = consoleWarn; // eslint-disable-line no-console
done(...args);
});
}
2 changes: 2 additions & 0 deletions modules/__tests__/TestSequences/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export {
} from "./HashChangeTransitionHook";
export { default as InitialLocationNoKey } from "./InitialLocationNoKey";
export { default as InitialLocationHasKey } from "./InitialLocationHasKey";
export { default as LinkSamePath } from "./LinkSamePath";
export { default as LinkSamePathWarning } from "./LinkSamePathWarning";
export { default as Listen } from "./Listen";
export {
default as LocationPathnameAlwaysDecoded
Expand Down
7 changes: 6 additions & 1 deletion modules/createBrowserHistory.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import warning from "tiny-warning";
import invariant from "tiny-invariant";

import { createLocation } from "./LocationUtils";
import { createLocation, shouldReplace } from "./LocationUtils";
import {
addLeadingSlash,
stripTrailingSlash,
Expand Down Expand Up @@ -253,6 +253,10 @@ function createBrowserHistory(props = {}) {
);
}

function link(path, state) {
shouldReplace(history.location, path, state) ? replace(path, state) : push(path, state);
}

function go(n) {
globalHistory.go(n);
}
Expand Down Expand Up @@ -320,6 +324,7 @@ function createBrowserHistory(props = {}) {
createHref,
push,
replace,
link,
go,
goBack,
goForward,
Expand Down
5 changes: 5 additions & 0 deletions modules/createHashHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ function createHashHistory(props = {}) {
);
}

function link(path){
push(path);
}

function go(n) {
warning(
canGoWithoutReload,
Expand Down Expand Up @@ -339,6 +343,7 @@ function createHashHistory(props = {}) {
createHref,
push,
replace,
link,
go,
goBack,
goForward,
Expand Down
7 changes: 6 additions & 1 deletion modules/createMemoryHistory.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import warning from "tiny-warning";

import { createPath } from "./PathUtils";
import { createLocation } from "./LocationUtils";
import { createLocation, shouldReplace } from "./LocationUtils";
import createTransitionManager from "./createTransitionManager";

function clamp(n, lowerBound, upperBound) {
Expand Down Expand Up @@ -118,6 +118,10 @@ function createMemoryHistory(props = {}) {
);
}

function link(path, state){
shouldReplace(history.location, path, state) ? replace(path, state) : push(path, state);
}

function go(n) {
const nextIndex = clamp(history.index + n, 0, history.entries.length - 1);

Expand Down Expand Up @@ -174,6 +178,7 @@ function createMemoryHistory(props = {}) {
createHref,
push,
replace,
link,
go,
goBack,
goForward,
Expand Down