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

Adeola Adeyemo J.
4 min readMar 19, 2021
Photo by Florian Rieder on Unsplash

Introduction

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 react-router,
  • and the default browser dialog which blocks other forms of navigation using thebeforeunload event 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.

You need a basic understanding of JavaScript, React JS, and some knowledge of the React-Router to follow along.

The Beforeunload event

According to MDN,

The beforeunload event 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

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 location and action the user is attempting to navigate to. Return a string to show a prompt to the user or true to allow the transition.

The Prompt gets the handleBlockedRoute function as its message prop. Note the following:

  • handleBlockedRoute gets the next location like mentioned in the documentation as a parameter. It also determines whether the prompt is shown or not
  • The state variable, confirmedNavigation tracks 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
  • lastLocation holds the last location that was blocked as its name suggests
  • showModal opens the Confirmation Modal and sets the next location that was initial blocked into lastLocation .

So the 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:

  • The handleConfirmNavigationClick function closes the modal, then sets the confirmNavigation state variable to true
  • The useEffect hook watches for changes in confirmNavigation . If the navigation routing has been confirmed ( confirmNavigation is true) and there exists a lastLocation (the location that was blocked earlier), we trigger the route change using the useHistory hook.

Blocking Non-React routes

As mentioned earlier, we will use the beforeunload event to block non-react routing

Note the following:

  • isModalOpen determines whether the modal is open or not. It is changed by two functions: openModal and closeModal
  • shouldUnload determines whether the Browser dialog should show when the beforeunload event 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

The content and title are passed to the modal in practice.

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.

You can also share your feedback with me on Twitter, follow me here on Medium, or Github.

--

--

Adeola Adeyemo J.

Senior Frontend / Founding Engineer | Open to new opportunities | https://deolaj.com