Skip to content

Commit 47f0599

Browse files
committed
[changed] Remove preserveScrollPosition, add scrollStrategy
This removes support for `preserveScrollPosition` due to its unforunate naming. Instead, we introduce three scrolling strategies: * `none`: Router doesn't scroll the window when routes change * `scrollToTop` (default): Router always scrolls to top when routes change * `imitateBrowser`: Router tries to act like browser acts with server-rendered pages: it scrolls to top when clicking on links, but tries to restore position when navigating back and forward You can only specify these on <Routes />. Per-route overrides are not supported, but you can supply a custom strategy object. This also fixes #252. Migration path: The default changed from what corresponded to `imitateBrowser`, to `scrollToTop`. If router's server-rendered scrolling imitation worked well for you, you must now specify it explicitly: ``` // before <Routes> <Routes preserveScrollPosition={false}> // after <Routes scrollStrategy='imitateBrowser'> ``` If you wish router to not try to manage scrolling, you must opt out: ``` // before <Routes preserveScrollPosition={true}> // after <Routes scrollStrategy='none'> ``` Also, as a third option, you may now use the simple `scrollToTop` strategy.
1 parent c96e34d commit 47f0599

File tree

14 files changed

+315
-136
lines changed

14 files changed

+315
-136
lines changed

modules/.DS_Store

6 KB
Binary file not shown.

modules/actions/LocationActions.js

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,96 @@
1+
var supportsHistory = require('../utils/supportsHistory');
2+
var HistoryLocation = require('../locations/HistoryLocation');
3+
var RefreshLocation = require('../locations/RefreshLocation');
14
var LocationDispatcher = require('../dispatchers/LocationDispatcher');
5+
var ActionTypes = require('../constants/ActionTypes');
6+
var warning = require('react/lib/warning');
27
var isAbsoluteURL = require('../utils/isAbsoluteURL');
38
var makePath = require('../utils/makePath');
49

510
function loadURL(url) {
611
window.location = url;
712
}
813

14+
var _location = null;
15+
var _isDispatching = false;
16+
var _previousPath = null;
17+
18+
function dispatchAction(actionType, operation) {
19+
if (_isDispatching)
20+
throw new Error('Cannot handle ' + actionType + ' in the middle of another action.');
21+
22+
_isDispatching = true;
23+
24+
var scrollPosition = {
25+
x: window.scrollX,
26+
y: window.scrollY
27+
};
28+
29+
if (typeof operation === 'function')
30+
operation(_location);
31+
32+
var path = _location.getCurrentPath();
33+
LocationDispatcher.handleViewAction({
34+
type: actionType,
35+
path: path,
36+
scrollPosition: scrollPosition
37+
});
38+
39+
_isDispatching = false;
40+
_previousPath = path;
41+
}
42+
43+
function handleChange() {
44+
var path = _location.getCurrentPath();
45+
46+
// Ignore changes inside or caused by dispatchAction
47+
if (!_isDispatching && path !== _previousPath) {
48+
dispatchAction(ActionTypes.POP);
49+
}
50+
}
51+
952
/**
1053
* Actions that modify the URL.
1154
*/
1255
var LocationActions = {
1356

14-
PUSH: 'push',
15-
REPLACE: 'replace',
16-
POP: 'pop',
17-
UPDATE_SCROLL: 'update-scroll',
57+
getLocation: function () {
58+
return _location;
59+
},
60+
61+
setup: function (location) {
62+
// When using HistoryLocation, automatically fallback
63+
// to RefreshLocation in browsers that do not support
64+
// the HTML5 history API.
65+
if (location === HistoryLocation && !supportsHistory())
66+
location = RefreshLocation;
67+
68+
if (_location !== null) {
69+
warning(
70+
_location === location,
71+
'Cannot use location %s, already using %s', location, _location
72+
);
73+
return;
74+
}
75+
76+
_location = location;
77+
78+
if (_location !== null) {
79+
dispatchAction(ActionTypes.SETUP, function (location) {
80+
if (typeof location.setup === 'function')
81+
location.setup(handleChange);
82+
});
83+
}
84+
},
85+
86+
teardown: function () {
87+
if (_location !== null) {
88+
if (typeof _location.teardown === 'function')
89+
_location.teardown();
90+
91+
_location = null;
92+
}
93+
},
1894

1995
/**
2096
* Transitions to the URL specified in the arguments by pushing
@@ -24,9 +100,9 @@ var LocationActions = {
24100
if (isAbsoluteURL(to)) {
25101
loadURL(to);
26102
} else {
27-
LocationDispatcher.handleViewAction({
28-
type: LocationActions.PUSH,
29-
path: makePath(to, params, query)
103+
dispatchAction(ActionTypes.PUSH, function (location) {
104+
var path = makePath(to, params, query);
105+
location.push(path);
30106
});
31107
}
32108
},
@@ -39,9 +115,9 @@ var LocationActions = {
39115
if (isAbsoluteURL(to)) {
40116
loadURL(to);
41117
} else {
42-
LocationDispatcher.handleViewAction({
43-
type: LocationActions.REPLACE,
44-
path: makePath(to, params, query)
118+
dispatchAction(ActionTypes.REPLACE, function (location) {
119+
var path = makePath(to, params, query);
120+
location.replace(path);
45121
});
46122
}
47123
},
@@ -50,18 +126,8 @@ var LocationActions = {
50126
* Transitions to the previous URL.
51127
*/
52128
goBack: function () {
53-
LocationDispatcher.handleViewAction({
54-
type: LocationActions.POP
55-
});
56-
},
57-
58-
/**
59-
* Updates the window's scroll position to the last known position
60-
* for the current URL path.
61-
*/
62-
updateScroll: function () {
63-
LocationDispatcher.handleViewAction({
64-
type: LocationActions.UPDATE_SCROLL
129+
dispatchAction(ActionTypes.POP, function (location) {
130+
location.pop();
65131
});
66132
}
67133

modules/components/Routes.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var Route = require('../components/Route');
77
var ActiveDelegate = require('../mixins/ActiveDelegate');
88
var PathListener = require('../mixins/PathListener');
99
var RouteStore = require('../stores/RouteStore');
10+
var ScrollStore = require('../stores/ScrollStore');
1011
var Path = require('../utils/Path');
1112
var Promise = require('../utils/Promise');
1213
var Redirect = require('../utils/Redirect');
@@ -51,9 +52,11 @@ function maybeUpdateScroll(routes) {
5152
return;
5253

5354
var currentRoute = routes.getCurrentRoute();
55+
var scrollPosition = ScrollStore.getScrollPosition();
5456

55-
if (!routes.props.preserveScrollPosition && currentRoute && !currentRoute.props.preserveScrollPosition)
56-
LocationActions.updateScroll();
57+
if (currentRoute && scrollPosition) {
58+
window.scrollTo(scrollPosition.x, scrollPosition.y);
59+
}
5760
}
5861

5962
/**

modules/constants/ActionTypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
var keyMirror = require('react/lib/keyMirror');
2+
3+
var ActionTypes = keyMirror({
4+
SETUP: null,
5+
PUSH: null,
6+
REPLACE: null,
7+
POP: null
8+
});
9+
10+
module.exports = ActionTypes;

modules/locations/HistoryLocation.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,10 @@ var HistoryLocation = {
3434

3535
push: function (path) {
3636
window.history.pushState({ path: path }, '', path);
37-
_onChange();
3837
},
3938

4039
replace: function (path) {
4140
window.history.replaceState({ path: path }, '', path);
42-
_onChange();
4341
},
4442

4543
pop: function () {

modules/locations/MemoryLocation.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,19 @@ var warning = require('react/lib/warning');
22

33
var _lastPath = null;
44
var _currentPath = null;
5-
var _onChange;
65

76
/**
87
* A Location that does not require a DOM.
98
*/
109
var MemoryLocation = {
1110

12-
setup: function (onChange) {
13-
_onChange = onChange;
14-
},
15-
1611
push: function (path) {
1712
_lastPath = _currentPath;
1813
_currentPath = path;
19-
_onChange();
2014
},
2115

2216
replace: function (path) {
2317
_currentPath = path;
24-
_onChange();
2518
},
2619

2720
pop: function () {
@@ -32,7 +25,6 @@ var MemoryLocation = {
3225

3326
_currentPath = _lastPath;
3427
_lastPath = null;
35-
_onChange();
3628
},
3729

3830
getCurrentPath: function () {

modules/mixins/PathListener.js

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
var React = require('react');
1+
var LocationActions = require('../actions/LocationActions');
22
var DefaultLocation = require('../locations/DefaultLocation');
33
var HashLocation = require('../locations/HashLocation');
44
var HistoryLocation = require('../locations/HistoryLocation');
55
var RefreshLocation = require('../locations/RefreshLocation');
6+
var NoneStrategy = require('../strategies/NoneStrategy');
7+
var ScrollToTopStrategy = require('../strategies/ScrollToTopStrategy');
8+
var ImitateBrowserStrategy = require('../strategies/ImitateBrowserStrategy');
69
var PathStore = require('../stores/PathStore');
10+
var ScrollStore = require('../stores/ScrollStore');
711

812
/**
913
* A hash of { name, location } pairs.
@@ -14,6 +18,15 @@ var NAMED_LOCATIONS = {
1418
refresh: RefreshLocation
1519
};
1620

21+
/**
22+
* A hash of { name, scrollStrategy } pairs.
23+
*/
24+
var NAMED_SCROLL_STRATEGIES = {
25+
none: NoneStrategy,
26+
scrollToTop: ScrollToTopStrategy,
27+
imitateBrowser: ImitateBrowserStrategy
28+
};
29+
1730
/**
1831
* A mixin for components that listen for changes to the current
1932
* URL path.
@@ -26,12 +39,20 @@ var PathListener = {
2639

2740
if (typeof location === 'string' && !(location in NAMED_LOCATIONS))
2841
return new Error('Unknown location "' + location + '", see ' + componentName);
29-
}
42+
},
43+
44+
scrollStrategy: function (props, propName, componentName) {
45+
var scrollStrategy = props[propName];
46+
47+
if (typeof scrollStrategy === 'string' && !(scrollStrategy in NAMED_SCROLL_STRATEGIES))
48+
return new Error('Unknown scrollStrategy "' + scrollStrategy + '", see ' + componentName);
49+
},
3050
},
3151

3252
getDefaultProps: function () {
3353
return {
34-
location: DefaultLocation
54+
location: DefaultLocation,
55+
scrollStrategy: ScrollToTopStrategy
3556
};
3657
},
3758

@@ -48,8 +69,22 @@ var PathListener = {
4869
return location;
4970
},
5071

72+
/**
73+
* Gets the scroll strategy object this component uses to
74+
* restore scroll position when the path changes.
75+
*/
76+
getScrollStrategy: function () {
77+
var scrollStrategy = this.props.scrollStrategy;
78+
79+
if (typeof scrollStrategy === 'string')
80+
return NAMED_SCROLL_STRATEGIES[scrollStrategy];
81+
82+
return scrollStrategy;
83+
},
84+
5185
componentWillMount: function () {
52-
PathStore.setup(this.getLocation());
86+
ScrollStore.setup(this.getScrollStrategy());
87+
LocationActions.setup(this.getLocation());
5388

5489
if (this.updatePath)
5590
this.updatePath(PathStore.getCurrentPath());
@@ -61,6 +96,8 @@ var PathListener = {
6196

6297
componentWillUnmount: function () {
6398
PathStore.removeChangeListener(this.handlePathChange);
99+
ScrollStore.teardown();
100+
LocationActions.teardown();
64101
},
65102

66103
handlePathChange: function () {

0 commit comments

Comments
 (0)