question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Prevent user from exiting a route

See original GitHub issue

For example, say the user is halfway through typing a message in a blog post; if they click on a link that could take them away from the route, we need the ability to cancel the route change. Which we do, using triggersExit and stop().

However, the reality is more complex; we don’t want to stop the user from changing routes, only warn them that they will lose data if they do and give them a cancel option. So we popup a modal with a confirm / cancel option. The problem now is that this requires a callback, so the triggersExit function returns without having called stop() and thus continues, regardless of the user choice.

Could we not have a continue() function that must be called in order for the route to change, or alternatively a callback(cancel) type method structure?

Issue Analytics

  • State:open
  • Created 8 years ago
  • Comments:22 (5 by maintainers)

github_iconTop GitHub Comments

9reactions
space-aliencommented, Dec 13, 2015

I have put together the following workaround for this problem that effectively ‘prevents’ route changes when needed, e.g. when the user has unsaved changes. This allows other code to rely on the router to enforce handling unsaved changes, without needing to run additional checks.

It seems we can’t simply stop() unwanted route changes without having an inconsistent address bar, so my approach ‘rewinds’ the route change as cleanly as possible. It copes nicely with the user pressing the browser back button.

In the code below, If there are unsaved changes, preventRouteChange() uses window.confirm() to prompt the user synchronously, using the browser’s built-in prompt. This is the most robust approach because it blocks the viewport and prevents the user from clicking the back/forward buttons. Alternatively, you might use setTimeout to fire off an asynchronous warning prompt of your own design after the route change has been prevented: targetContext provides the user’s intended destination for your ‘yes’ callback. But beware, if you do this, the user can still press the back/forward buttons while your prompt is displayed! So be sure to handle that if necessary.

Where appropriate, I recommend combining this solution with window.onbeforeunload which will also catch attempts to navigate away from the app - e.g. closing the browser window. Another benefit of using window.confirm() is that the prompt looks and functions the same as the window.onbeforeunload prompt, which means you have the same user experience for both navigating away and closing the window.

Obviously, it would be better if it were possible to prevent route changes globally, but this is currently not possible.

One last point that came up with all of this: Perhaps FlowRouter should detect dangerous calls to go() within route triggers, and handle them better - e.g. queue them for execution, or abort current route handling and move to the new route requested by go()? My workaround was to wrap the call in setTimeout().

I’d be interested to hear your comments.

// Prevent routing when there are unsaved changes
// ----------------------------------------------

// This function will be called on every route change.
// Return true to 'prevent' the route from changing.
function preventRouteChange (targetContext) {
  if (Session.get('unsavedChanges')) {
    if (!window.confirm('Unsaved changes will be lost. Are you sure?')) {
      return true;
    }
    Session.set('unsavedChanges', false);
  }
  return false;
}

// Workaround FlowRouter to provide the ability to prevent route changes
var previousPath,
  isReverting,
  routeCounter = 0,
  routeCountOnPopState;

window.onpopstate = function () {
  // For detecting whether the user pressed back/forward button.
  routeCountOnPopState = routeCounter;
};

FlowRouter.triggers.exit([function (context, redirect, stop) {
  // Before we leave the route, cache the current path.
  previousPath = context.path;
}]);

FlowRouter.triggers.enter([function (context, redirect, stop) {
  routeCounter++;

  if (isReverting) {
    isReverting = false;
    // This time, we are simply 'undoing' the previous (prevented) route change.
    // So we don't want to actually fire any route actions.
    stop();
  }
  else if (preventRouteChange(context)) {
    // This route change is not allowed at the present time.

    // Prevent the route from firing.
    stop();

    isReverting = true;

    if (routeCountOnPopState == routeCounter - 1) {
      // This route change was due to browser history - e.g. back/forward button was clicked.
      // We want to undo this route change without overwriting the current history entry.
      // We can't use redirect() because it would overwrite the history entry we are trying
      // to preserve.

      // setTimeout allows FlowRouter to finish handling the current route change.
      // Without it, calling FlowRouter.go() at this stage would cause problems (we would
      // ultimately end up at the wrong URL, i.e. that of the current context).
      setTimeout(function () {
        FlowRouter.go(previousPath);
      });
    }
    else {
      // This is a regular route change, e.g. user clicked a navigation control.
      // setTimeout for the same reasons as above.
      setTimeout(function () {
        // Since we know the user didn't navigate using browser history, we can safely use
        // history.back(), keeping the browser history clean.
        history.back();
      });
    }
  }
}]);
5reactions
NTruchaudcommented, Jun 19, 2017

Up here, Do we have an “official way” to do this now ?

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to prevent user from going back in react-router v6?
I'm using react-router-dom for routing in my ReactJS app. And I want to prevent a user from going back after login i.e I...
Read more >
How to block user from leaving a page on a single page app
So we can block the user from leaving the page on a single page app? · Navigate within the SPA with history library's...
Read more >
Prevent a route activation based on user permissions with the ...
However, for a better user experience we may want to prevent the user from entering areas of our application he's not authorized to...
Read more >
How can I stop a route from a route - Apache Camel
The CamelContext provides API for managing routes at runtime. It has a stopRoute(id) and startRoute(id) methods. Stopping a route during routing an existing...
Read more >
Route Call Block - Genesys Documentation
The Routing Priority tab and the Targeting Options in the Advanced tab (Clear targets from queue if this block times out and Early...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found