diff --git a/modules/locations/HashLocation.js b/modules/locations/HashLocation.js index 871f806174..c041fe3995 100644 --- a/modules/locations/HashLocation.js +++ b/modules/locations/HashLocation.js @@ -67,38 +67,33 @@ var HashLocation = { // Do this BEFORE listening for hashchange. ensureSlash(); - if (_isListening) - return; + if (!_isListening) { + if (window.addEventListener) { + window.addEventListener('hashchange', onHashChange, false); + } else { + window.attachEvent('onhashchange', onHashChange); + } - if (window.addEventListener) { - window.addEventListener('hashchange', onHashChange, false); - } else { - window.attachEvent('onhashchange', onHashChange); + _isListening = true; } - - _isListening = true; }, removeChangeListener: function(listener) { - for (var i = 0, l = _changeListeners.length; i < l; i ++) { - if (_changeListeners[i] === listener) { - _changeListeners.splice(i, 1); - break; + _changeListeners = _changeListeners.filter(function (l) { + return l !== listener; + }); + + if (_changeListeners.length === 0) { + if (window.removeEventListener) { + window.removeEventListener('hashchange', onHashChange, false); + } else { + window.removeEvent('onhashchange', onHashChange); } - } - - if (window.removeEventListener) { - window.removeEventListener('hashchange', onHashChange, false); - } else { - window.removeEvent('onhashchange', onHashChange); - } - if (_changeListeners.length === 0) _isListening = false; + } }, - - push: function (path) { _actionType = LocationActions.PUSH; window.location.hash = Path.encode(path); diff --git a/modules/locations/HistoryLocation.js b/modules/locations/HistoryLocation.js index 62b945d669..ccecbddb28 100644 --- a/modules/locations/HistoryLocation.js +++ b/modules/locations/HistoryLocation.js @@ -38,38 +38,33 @@ var HistoryLocation = { addChangeListener: function (listener) { _changeListeners.push(listener); - if (_isListening) - return; + if (!_isListening) { + if (window.addEventListener) { + window.addEventListener('popstate', onPopState, false); + } else { + window.attachEvent('popstate', onPopState); + } - if (window.addEventListener) { - window.addEventListener('popstate', onPopState, false); - } else { - window.attachEvent('popstate', onPopState); + _isListening = true; } - - _isListening = true; }, removeChangeListener: function(listener) { - for (var i = 0, l = _changeListeners.length; i < l; i ++) { - if (_changeListeners[i] === listener) { - _changeListeners.splice(i, 1); - break; + _changeListeners = _changeListeners.filter(function (l) { + return l !== listener; + }); + + if (_changeListeners.length === 0) { + if (window.addEventListener) { + window.removeEventListener('popstate', onPopState); + } else { + window.removeEvent('popstate', onPopState); } - } - - if (window.addEventListener) { - window.removeEventListener('popstate', onPopState); - } else { - window.removeEvent('popstate', onPopState); - } - if (_changeListeners.length === 0) _isListening = false; + } }, - - push: function (path) { window.history.pushState({ path: path }, '', Path.encode(path)); History.length += 1; diff --git a/modules/locations/TestLocation.js b/modules/locations/TestLocation.js index 036516765d..149c22ad08 100644 --- a/modules/locations/TestLocation.js +++ b/modules/locations/TestLocation.js @@ -28,8 +28,6 @@ var TestLocation = { updateHistoryLength(); }, - removeChangeListener: function () {}, - push: function (path) { TestLocation.history.push(path); updateHistoryLength(); diff --git a/modules/utils/createRouter.js b/modules/utils/createRouter.js index 2109d21fa6..bc2dafd650 100644 --- a/modules/utils/createRouter.js +++ b/modules/utils/createRouter.js @@ -157,6 +157,14 @@ function createRouter(options) { var state = {}; var nextState = {}; var pendingTransition = null; + var changeListener = null; + + function cancelPendingTransition() { + if (pendingTransition) { + pendingTransition.abort(new Cancellation); + pendingTransition = null; + } + } function updateState() { state = nextState; @@ -192,6 +200,7 @@ function createRouter(options) { defaultRoute: null, notFoundRoute: null, + isRunning: false, /** * Adds routes to this router from the given children object (see ReactChildren). @@ -317,10 +326,7 @@ function createRouter(options) { * hooks wait, the transition is fully synchronous. */ dispatch: function (path, action, callback) { - if (pendingTransition) { - pendingTransition.abort(new Cancellation); - pendingTransition = null; - } + cancelPendingTransition(); var prevPath = state.path; if (prevPath === path) @@ -396,6 +402,11 @@ function createRouter(options) { * Router.*Location objects (e.g. Router.HashLocation or Router.HistoryLocation). */ run: function (callback) { + invariant( + !this.isRunning, + 'Router is already running' + ); + var dispatchHandler = function (error, transition) { pendingTransition = null; @@ -412,7 +423,7 @@ function createRouter(options) { router.dispatch(location, null, dispatchHandler); } else { // Listen for changes to the location. - var changeListener = function (change) { + changeListener = function (change) { router.dispatch(change.path, change.type, dispatchHandler); }; @@ -421,11 +432,20 @@ function createRouter(options) { // Bootstrap using the current path. router.dispatch(location.getCurrentPath(), null, dispatchHandler); + + this.isRunning = true; } }, - teardown: function() { - location.removeChangeListener(this.changeListener); + stop: function () { + cancelPendingTransition(); + + if (location.removeChangeListener && changeListener) { + location.removeChangeListener(changeListener); + changeListener = null; + } + + this.isRunning = false; } }, @@ -461,8 +481,8 @@ function createRouter(options) { this.setState(state); }, - componentWillUnmount: function() { - router.teardown(); + componentWillUnmount: function () { + router.stop(); }, render: function () {