Cancel navigation to a different page in a React app

When we are dealing with form input, we need to cancel navigation to a different page to ensure that the form is saved. This is a bit tricky than it sounds. How should the app behave if the user

  1. Closes the browser.
  2. Moves to a different website.
  3. Moves to another page within the same website.

Navigating to a different site or closing the browser

There is not much that a React app can do to handle the cases 1. and 2. The onbeforeunload function should be used to check if the user is navigating away from a site.

window.onbeforeunload = function() {   
  return "Are you sure you want to navigate away?"; 
}

The onbeforeunload function shows a confirm dialog. If the user wants to stay on the site, the navigation is cancelled.

For a functional component, the code will look like so:

import { useState, useEffect } from "react";

export default function App() {
  const [dirty, setDirty] = useState(false);
  useEffect(() => {
    window.onbeforeunload = function () {
      if (dirty) {
        return "Are you sure you want to navigate away?";
      }
    };
  }, [dirty]);
  function handleChange(e) {
    setDirty(!!e.target.value);
  }
  return (
    <div className="App">
      <input type="text" onChange={handleChange} />
    </div>
  );
}

The code is available in CodeSandbox if you want to try it out. Simply type some text into the textbox and close the browser.

Navigating within the same app with react-router v3

This post was written in 2017 when react-router was in version 3. For 2021 / react-router v5 code, please scroll below.

There are more options available if the user is navigating to a different page on the same app. A custom dialog (another React component) can be presented to the user which asks the user to save the form and continue navigation or cancel the navigation.

React router has hooks which run when certain events happen. One such event is “Route Leave”. The setRouteLeaveHook can used to set a function which will get triggered when the user leaves the current route or page.

componentWillMount() {     
  this.unregisterLeaveHook = 
    this.props.router.setRouteLeaveHook(
      this.props.route,
      this.routerWillLeave.bind(this)
    );
}

In the routerWillLeave function, returning false will cancel navigation. If the user has not saved the work, we return false to cancel the navigation as shown below.

routerWillLeave(nextLocation) {   
  return !this.isDirty;         
}

Finally, if the user pressed OK to leave the page, then we unregister the hook we set earlier. The unregister function is returned when we set the hook earlier.

componentWillUnmount() {     
  this.unregisterLeaveHook(); 
}

The above code snippets are the bare minimum that should be present in your custom solution. There can be more variations to the above code – like opening up custom dialog, proceeding with various actions in response to button clicks, etc.

Navigating within the same app with react-router v5

We will create an app with two pages: Home page and About page. Home page has a textbox. When the user enters some text in the textbox, and navigates to About page, we ask the user if he really wants to leave.

Modify App.js (root component) like so:

import { BrowserRouter, Route, Link, Switch } from "react-router-dom";
import About from "./About";
import Main from "./Main";

export default function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Link to="/">Home</Link> | <Link to="/about">About</Link>
        <Switch>
          <Route path="/about" component={About} />
          <Route component={Main} />
        </Switch>
      </BrowserRouter>
    </div>
  );
}

Note the links at the top of the page. And the route definition for our app.

Create an About page:

export default function About() {
  return <div>About page</div>;
}

About page has only a div!

Create the Main component:

import { useState } from "react";
import { Prompt } from "react-router-dom";

export default function Main() {
  const [dirty, setDirty] = useState(false);

  function handleChange(e) {
    setDirty(!!e.target.value);
  }

  return (
    <div>
      <input type="text" onChange={handleChange} />
      <Prompt when={dirty} message="Are you sure you want to leave?" />
    </div>
  );
}

The main component has a textbox. When the user types in some text, we set the dirty state to true and vice-versa. There is also a Prompt component. When the user navigates to another page, if the dirty flag is set, then it shows the message – “Are you sure you want to leave” in a confirm box.

Confirm box is the default implementation. If you want to change that, using a custom Popup, then you should override the getUserConfirmation prop in the BrowserRouter.

The entire code is available in a CodeSandbox. To try out the code, please check this demo page.

Related Posts

4 thoughts on “Cancel navigation to a different page in a React app

    1. Updated the article with the latest react-router-dom v5 code and using functional components. Hope it helps.

Leave a Reply

Your email address will not be published.