How to create a Custom Route Blocking Modal in React
A short tutorial on how to block navigation using both the beforeunload event and React Router Prompt in a hybrid solution
Recently, I faced a problem where I needed to show a message to block a user from navigating away from the page after performing some action in a React application. So I did some research on solutions that could give some control. While searching for this solution, I stumbled on this article that also helped in drafting a solution by taking advantage of the React-Router library.
To extend that solution, I will walk you through some basic steps to create a hybrid solution. This solution involves:
- A custom modal to block routing to other React pages using
- and the default browser dialog which blocks other forms of navigation using the
beforeunloadevent to catch other corner cases.
This hybrid solution can be extended to fit other use cases. The Custom Modal created in this tutorial is in this Sandbox if you want to skip the explanation.
The Beforeunload event
beforeunloadevent is fired when the window, the document and its resources are about to be unloaded. The document is still visible and the event is still cancelable at this point.
With this event, we can stop users from navigating by triggering a dialog that asks if the user wants to leave the page or not.
React-router helps with routing in React applications, and it provides a component, Prompt, which helps with route blocking logic. We will use this component in this solution.
Next, we will create a custom modal in React.
Creating the Base Modal
To create a modal in React, we use portals. According to the official documentation,
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
In this snippet, we create an element that represents the modal (div) and we store it in a ref. Then we append this modal (div) to an element (with the id of ‘modal’) already present in the DOM.
The modal is styled using
styled-components . Then we use this modal element to wrap our modal body in a new component.
In this component, we basically cover the cases when the modal is closed by either clicking the close button or clicking on the outer area of the modal. The scrolling container helps to catch cases where the Modal content overflows the viewport.
This is a very simple modal and does not cover making the modal accessible to keyboard users. To make your modal more accessible, check out this article.
Next, we will use this component to create our confirmation modal
Creating the Confirmation Modal
In this section, we will separate the React and non-react route blocking implementations. Then merge both in one file.
Blocking React routes
First, as we mentioned earlier, we will use the
Prompt component from React Router. According to the documentation,
Prompt has a message prop, a function which:
Will be called with the next
actionthe user is attempting to navigate to. Return a string to show a prompt to the user or
trueto allow the transition.
Prompt gets the
handleBlockedRoute function as its message prop. Note the following:
handleBlockedRoutegets the next location like mentioned in the documentation as a parameter. It also determines whether the prompt is shown or not
- The state variable,
confirmedNavigationtracks whether the user has decided to leave the page through the modal
- A prop
isBlocked, which we will use later, helps us to signal to the Modal whether the navigation should be stopped or not
lastLocationholds the last location that was blocked as its name suggests
showModalopens the Confirmation Modal and sets the next location that was initial blocked into
handleBlockedRoute first determines whether the route should be blocked based on the
isBlocked prop and the
confirmedNavigation state variable. If the route should be blocked, it triggers the confirmation modal and also sets the
lastLocation state variable.
So if a user decides to leave the page after the modal has been triggered, we trigger the
handleConfirmNavigationClick function. Note the following:
handleConfirmNavigationClickfunction closes the modal, then sets the
confirmNavigationstate variable to true
useEffecthook watches for changes in
confirmNavigation. If the navigation routing has been confirmed (
confirmNavigationis true) and there exists a
lastLocation(the location that was blocked earlier), we trigger the route change using the
Blocking Non-React routes
As mentioned earlier, we will use the
beforeunload event to block non-react routing
Note the following:
isModalOpendetermines whether the modal is open or not. It is changed by two functions:
shouldUnloaddetermines whether the Browser dialog should show when the
beforeunloadevent is triggered
First, we add the
setShouldUnload function to the
useEffect hook that allows navigation routing in react. Then we create a
useEffect hook that blocks non-react routing.
Putting it all together
Merging everything with some styling for the Custom Modal, we get this
title are passed to the modal in practice.
To view the modal and dialog in action, we have created two pages to illustrate the blocked navigation.
Finally, note that the
Prompt component should be used within a Router.
Thank you for following along. This is the end of the tutorial. Please leave a comment or feedback below if you have any. I hope you found this useful. Once again, to view the modal in action, check out this Sandbox.