Disabling back button in React with react-router v5

Ye Joo Park
5 min readAug 25, 2019

--

Disabling the back button is a clear UX no-no. But there are rare cases where disabling the back button is an absolute necessity. Think of a web-based exam application where test-takers cannot navigate back to the previous section once they’ve moved on.

Here, we’ll work on a simple app with only 3 pages to see how we can prevent back navigation when using react-router v5. The code will also work with v4 as v5 is fully backward compatible with v4. Our component tree for the app is depicted below:

Super simple app

The requirements for the app are:

  1. Users can navigate to the next page by clicking on a button on the page.
  2. Users cannot “go back” to the previous page once they have moved onto the next page.
  3. When a user presses “back” button in the browser, all states should be preserved.

Fulfilling the first requirement is easy. We’ll create a button to navigate to the next page. We need a plan to tackle the other two. Our initial plan is to listen to the user’s “back” event and send them “forward” again whenever the user navigates backward. To achieve this, we’ll do the following:

  1. Create a top-level <Route> in <BrowserRouter> that matches all URLs (/) and render App component.
  2. Get history object by wrapping App component with withRouter higher-order component
  3. Listen to window object’s popstate event and go forward if the user navigates backward.

Here’s a running sandbox. Try to go back in page 2 or page 3. The app will bring you back to the current page even if you go back.

There’s one large problem though — your state gets lost!! In page 2, type anything in the input box and navigate back to see what happens.

Page 2 before pressing back button
Page 2 after pressing back button — the state is reset to the initial value

What happened?

The app has a global listener that listens to popstate, and moves the user forward whenever the user has navigated back using the navigate back event.

// Hey, a popstate event happened!
window.addEventListener("popstate", e => {
// Nope, go back to your page
this.props.history.go(1);
});

When you press the back button on page 2, you are taken back to page 1 although you might not notice it since it happens in a matter of milliseconds. Page2 component is unmounted and is mounted back immediately when the user is sent back. This is where our state gets lost.

What we really want: keep the user in the same component without leaving it

Here is what we really want.

Users need to stay in the same route

We want to prevent the back navigation altogether. But as easy as it sounds, there is no way to disable a back button or a navigation event. We’ll have to rely on a workaround.

When a user navigates to a new page, we’ll clone the location details and push that location object to router’s history stack.

Our previous history stack
What our new history stack will look like

To do this, we’ll use listen to history object and handle PUSH and POP actions. A PUSH action event is fired when new navigation occurs and the new location is pushed to the history stack (this doesn’t include navigations from pressing the back button or forward button). A POP action event is fired when a user goes back/forward (either by clicking the browser’s back/forward button, or using keyboard shortcuts, or through Javascript-fired event such as window.history.back()).

Recall that we are using a withRouter higher-order component and that we already have access to the history object. Below is a simplified version of our new code.

history.listen((newLocation, action) => {
if (action === "PUSH") {
if (locationChanges) {
// Clone location object and push it to history
history.push({
pathname: newLocation.pathname,
search: newLocation.search
});
}
} else {
// If a "POP" action event occurs,
// Send user back to the originating location
history.go(1);
}
});
}

See below for a running sandbox. Any state on page 2 is preserved even if you press the “back” button in the browser. Note that we’re no longer adding a global listener to the window object. We can use our history object to handle everything.

Here is a visual flow of what is happening. When a user presses the back button, the browser navigates to identical location.pathname and location.search, and is sent back to the originating location immediately.

Don’t forget:

  • You may have to handle changes in location.hash if your app has in-page links through hash — like <a href="#some-div">In-page link</a>.
  • Unless abso-mega-lutely necessary, you shouldn’t try to disable the back button in the first place.

You can see the complete code at https://github.com/subwaymatch/react-disable-back-button-example-v2/.

--

--