Share article:
Our product owner came to us with an interesting problem the other day, in the latest release of the MessageMedia Frontend we have a new payment portal for customers to pay invoices, when a user is in the process of paying a bill we need to block navigation, or in his words:
As a billing contact I want to confirm my intent to leave the payment portal when my payment is still processing so that I am aware that my payment hasn’t finished processing
There seems to be quite a few methods of achieving this but most are pre v4…
React Router have a great online documentation portal available, upon a quick search we find the <Prompt />
method.
Prompt the user before navigating away from a page.
Great! Let’s create a simple component to test.
import React from 'react' import PropTypes from 'prop-types' import { Prompt } from 'react-router-dom' const NavigationBlocker = (props) => ( <Prompt when={props.navigationBlocked} message="Are you sure you want to leave?" /> ) NavigationBlocker.propTypes = { navigationBlocked: PropTypes.bool.isRequired, } export default NavigationBlocker
/ Note: I’m specifically using react-router-dom
but you can use plain ol’ react-router
for this /
Users should now see a prompt when clicking on links between routes but what about browser actions eg. Back / Forward buttons, Reload or Close?
onbeforeunload
Once again you’re going to find many articles / stack overflow questions / tweets on how to implement this so I’d suggest –
As per the MDN Documentation:
The
WindowEventHandlers.onbeforeunload
event handler property contains the code executed when thebeforeunload
is sent.
The problem is there’s many ways to implement this but as of 2017 most browsers block custom messages (for security reasons), as we don’t need to worry about this we can use something as simple as:
// Enable navigation prompt window.onbeforeunload = () => true // Remove navigation prompt window.onbeforeunload = null
import React from 'react' import PropTypes from 'prop-types' import { Prompt } from 'react-router-dom' const NavigationBlocker = (props) => { if (props.navigationBlocked) { window.onbeforeunload = () => true } else { window.onbeforeunload = null } return ( <Prompt when={props.navigationBlocked} message="Are you sure you want to leave?" /> ) } NavigationBlocker.propTypes = { navigationBlocked: PropTypes.bool.isRequired, } export default NavigationBlocker
And just like that we have a navigation blocking component that we can easily turn on and off.
I would suggest extending it to be a Global Navigation Blocker from here, when a processing redux action is sent send another to block navigation, when it resolves send another to resolve.
This allows you to include the blocker once at app level rather than including every time you need it.
This is what my implementation ended up looking like:
import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Prompt } from 'react-router-dom' import { getNavigationBlocked } from '../navigation-selectors' const GlobalNavigationBlocker = (props) => { if (props.navigationBlocked) { window.onbeforeunload = () => true } else { window.onbeforeunload = null } return ( <Prompt when={props.navigationBlocked} message="Are you sure you want to leave?" /> ) } GlobalNavigationBlocker.propTypes = { navigationBlocked: PropTypes.bool.isRequired, } export const mapStateToProps = state => ({ navigationBlocked: getNavigationBlocked(state), }) export default connect(mapStateToProps)(GlobalNavigationBlocker)
Unfortunately because of how onbeforeunload
can be misused, modern browsers have implemented some safeguards, as described in the docs:
To combat unwanted pop-ups, some browsers don’t display prompts created in
beforeunload
event handlers unless the page has been interacted with; some don’t display them at all.
And
Various browsers ignore the result of the event and do not ask the user for confirmation at all … Firefox has a switch named
dom.disable_beforeunload
in about:config to enable this behaviour. (For eg.)
Or, TL;DR:
onbeforeunload
event will not fireonbeforeunload
can be turned off with browser settings
You, also, cannot specify what message is shown to the user during this interaction.
All in all, I hope this saved you some time researching!