Disabling back button in React with react-router v5
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:
The requirements for the app are:
- Users can navigate to the next page by clicking on a button on the page.
- Users cannot “go back” to the previous page once they have moved onto the next page.
- 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:
- Create a top-level
<Route>
in<BrowserRouter>
that matches all URLs (/
) and renderApp
component. - Get
history
object by wrappingApp
component withwithRouter
higher-order component - Listen to
window
object’spopstate
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.
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.
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.
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/.