From 91d43809eaf6161d752476857e6022034099c4bc Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 26 Nov 2014 20:46:36 +0100 Subject: [PATCH] [fixed] Abort pending transition on user navigation Previously, navigating (by clicking Back button or a link) while a transition in progress would put router into an inconsistent state. Now, a pending transition will be aborted and ignored by the router if user navigates away while it was pending. Fixes #479 --- modules/utils/Cancellation.js | 7 +++++++ modules/utils/Transition.js | 5 +++++ modules/utils/createRouter.js | 23 +++++++++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 modules/utils/Cancellation.js diff --git a/modules/utils/Cancellation.js b/modules/utils/Cancellation.js new file mode 100644 index 0000000000..dbba59c8cd --- /dev/null +++ b/modules/utils/Cancellation.js @@ -0,0 +1,7 @@ +/** + * Represents a cancellation caused by navigating away + * before the previous transition has fully resolved. + */ +function Cancellation() { } + +module.exports = Cancellation; diff --git a/modules/utils/Transition.js b/modules/utils/Transition.js index 08cf84bccd..136f860498 100644 --- a/modules/utils/Transition.js +++ b/modules/utils/Transition.js @@ -98,6 +98,11 @@ function Transition(path, retry) { assign(Transition.prototype, { abort: function (reason) { + if (this.isAborted) { + // First abort wins. + return; + } + this.abortReason = reason; this.isAborted = true; }, diff --git a/modules/utils/createRouter.js b/modules/utils/createRouter.js index 946217031e..a574673e3b 100644 --- a/modules/utils/createRouter.js +++ b/modules/utils/createRouter.js @@ -16,6 +16,7 @@ var supportsHistory = require('./supportsHistory'); var Transition = require('./Transition'); var PropTypes = require('./PropTypes'); var Redirect = require('./Redirect'); +var Cancellation = require('./Cancellation'); var Path = require('./Path'); /** @@ -43,7 +44,9 @@ function defaultAbortHandler(abortReason, location) { if (typeof location === 'string') throw new Error('Unhandled aborted transition! Reason: ' + abortReason); - if (abortReason instanceof Redirect) { + if (abortReason instanceof Cancellation) { + return; + } else if (abortReason instanceof Redirect) { location.replace(this.makePath(abortReason.to, abortReason.params, abortReason.query)); } else { location.pop(); @@ -141,6 +144,7 @@ function createRouter(options) { var onAbort = options.onAbort || defaultAbortHandler; var state = {}; var nextState = {}; + var pendingTransition = null; function updateState() { state = nextState; @@ -212,7 +216,14 @@ function createRouter(options) { 'You cannot use transitionTo with a static location' ); - location.push(this.makePath(to, params, query)); + var path = this.makePath(to, params, query); + + if (pendingTransition) { + // Replace so pending location does not stay in history. + location.replace(path); + } else { + location.push(path); + } }, /** @@ -265,6 +276,11 @@ function createRouter(options) { * hooks wait, the transition is fully synchronous. */ dispatch: function (path, action, callback) { + if (pendingTransition) { + pendingTransition.abort(new Cancellation()); + pendingTransition = null; + } + var prevPath = state.path; if (prevPath === path) return; // Nothing to do! @@ -306,6 +322,7 @@ function createRouter(options) { } var transition = new Transition(path, this.replaceWith.bind(this, path)); + pendingTransition = transition; transition.from(fromRoutes, components, function (error) { if (error || transition.isAborted) @@ -335,6 +352,8 @@ function createRouter(options) { */ run: function (callback) { function dispatchHandler(error, transition) { + pendingTransition = null; + if (error) { onError.call(router, error); } else if (transition.isAborted) {