Skip to content

Commit 457d944

Browse files
committed
[added] Router.History
[changed] goBack returns a boolean value Users can use Router.History.length to determine if the router has any history. router.goBack() is a no-op when the router does not have any history and emits a warning. Fixes #408
1 parent b9eaba1 commit 457d944

File tree

8 files changed

+129
-4
lines changed

8 files changed

+129
-4
lines changed

modules/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ exports.State = require('./mixins/State');
1717

1818
exports.create = require('./utils/createRouter');
1919
exports.run = require('./utils/runRouter');
20+
21+
exports.History = require('./utils/History');

modules/locations/HashLocation.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var LocationActions = require('../actions/LocationActions');
2+
var History = require('../utils/History');
23
var Path = require('../utils/Path');
34

45
/**
@@ -29,6 +30,9 @@ function ensureSlash() {
2930
var _changeListeners = [];
3031

3132
function notifyChange(type) {
33+
if (type === LocationActions.PUSH)
34+
History.length += 1;
35+
3236
var change = {
3337
path: getHashPath(),
3438
type: type
@@ -87,7 +91,7 @@ var HashLocation = {
8791

8892
pop: function () {
8993
_actionType = LocationActions.POP;
90-
window.history.back();
94+
History.back();
9195
},
9296

9397
getCurrentPath: getHashPath,

modules/locations/HistoryLocation.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var LocationActions = require('../actions/LocationActions');
2+
var History = require('../utils/History');
23
var Path = require('../utils/Path');
34

45
/**
@@ -51,6 +52,7 @@ var HistoryLocation = {
5152

5253
push: function (path) {
5354
window.history.pushState({ path: path }, '', Path.encode(path));
55+
History.length += 1;
5456
notifyChange(LocationActions.PUSH);
5557
},
5658

@@ -60,7 +62,7 @@ var HistoryLocation = {
6062
},
6163

6264
pop: function () {
63-
window.history.back();
65+
History.back();
6466
},
6567

6668
getCurrentPath: getWindowPath,

modules/locations/RefreshLocation.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ var RefreshLocation = {
1717
},
1818

1919
pop: function () {
20+
// History will always have length 1 when using full
21+
// page refreshes, so use window.history directly.
2022
window.history.back();
2123
},
2224

modules/locations/TestLocation.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
var invariant = require('react/lib/invariant');
12
var LocationActions = require('../actions/LocationActions');
3+
var History = require('../utils/History');
24

35
var _listener;
46

@@ -7,6 +9,10 @@ function notifyChange(type) {
79
_listener({ path: TestLocation.getCurrentPath(), type: type });
810
}
911

12+
function updateHistoryLength() {
13+
History.length = TestLocation.history.length;
14+
}
15+
1016
/**
1117
* A location that is convenient for testing and does not
1218
* require a DOM. You should manually setup TestLocation.history
@@ -19,20 +25,28 @@ var TestLocation = {
1925
addChangeListener: function (listener) {
2026
// TestLocation only ever supports a single listener at a time.
2127
_listener = listener;
28+
updateHistoryLength();
2229
},
2330

2431
push: function (path) {
2532
TestLocation.history.push(path);
33+
updateHistoryLength();
2634
notifyChange(LocationActions.PUSH);
2735
},
2836

2937
replace: function (path) {
38+
invariant(
39+
History.length,
40+
'You cannot replace the current path with no history'
41+
);
42+
3043
TestLocation.history[TestLocation.history.length - 1] = path;
3144
notifyChange(LocationActions.REPLACE);
3245
},
3346

3447
pop: function () {
3548
TestLocation.history.pop();
49+
updateHistoryLength();
3650
notifyChange(LocationActions.POP);
3751
},
3852

modules/utils/History.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var invariant = require('react/lib/invariant');
2+
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
3+
4+
var History = {
5+
6+
/**
7+
* Sends the browser back one entry in the history, if one is available.
8+
*/
9+
back: function () {
10+
invariant(
11+
canUseDOM,
12+
'Cannot use History.back without a DOM'
13+
);
14+
15+
// Do this first so that History.length will
16+
// be accurate in location change listeners.
17+
History.length -= 1;
18+
19+
window.history.back();
20+
},
21+
22+
/**
23+
* The current number of entries in the history.
24+
*/
25+
length: 1
26+
27+
};
28+
29+
module.exports = History;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
var expect = require('expect');
2+
var React = require('react');
3+
var { Foo, RedirectToFoo } = require('../../__tests__/TestHandlers');
4+
var TestLocation = require('../../locations/TestLocation');
5+
var Route = require('../../components/Route');
6+
var Router = require('../../index');
7+
var History = require('../History');
8+
9+
describe('History', function () {
10+
describe('on the initial page load', function () {
11+
it('has length 1', function () {
12+
expect(History.length).toEqual(1);
13+
});
14+
});
15+
16+
describe('after navigating to a route', function () {
17+
beforeEach(function () {
18+
TestLocation.history = [ '/foo' ];
19+
});
20+
21+
it('has length 2', function (done) {
22+
var routes = [
23+
<Route name="foo" handler={Foo}/>,
24+
<Route name="about" handler={Foo}/>
25+
];
26+
27+
var count = 0;
28+
29+
var router = Router.run(routes, TestLocation, function (Handler) {
30+
count += 1;
31+
32+
if (count === 2) {
33+
expect(History.length).toEqual(2);
34+
done();
35+
}
36+
});
37+
38+
router.transitionTo('about');
39+
});
40+
41+
describe('that redirects to another route', function () {
42+
it('has length 2', function (done) {
43+
var routes = [
44+
<Route name="foo" handler={Foo}/>,
45+
<Route name="about" handler={RedirectToFoo}/>
46+
];
47+
48+
var count = 0;
49+
50+
var router = Router.run(routes, TestLocation, function (Handler) {
51+
count += 1;
52+
53+
if (count === 2) {
54+
expect(History.length).toEqual(2);
55+
done();
56+
}
57+
});
58+
59+
router.transitionTo('about');
60+
});
61+
});
62+
});
63+
});

modules/utils/createRouter.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var supportsHistory = require('./supportsHistory');
1818
var Transition = require('./Transition');
1919
var PropTypes = require('./PropTypes');
2020
var Redirect = require('./Redirect');
21+
var History = require('./History');
2122
var Cancellation = require('./Cancellation');
2223
var Path = require('./Path');
2324

@@ -242,15 +243,23 @@ function createRouter(options) {
242243
},
243244

244245
/**
245-
* Transitions to the previous URL.
246+
* Transitions to the previous URL. Returns true if the router
247+
* was able to go back, false otherwise.
246248
*/
247249
goBack: function () {
248250
invariant(
249251
typeof location !== 'string',
250252
'You cannot use goBack with a static location'
251253
);
252254

253-
location.pop();
255+
if (History.length > 1) {
256+
location.pop();
257+
return true;
258+
}
259+
260+
warning(false, 'goBack() was ignored because there is no router history');
261+
262+
return false;
254263
},
255264

256265
/**

0 commit comments

Comments
 (0)