Using React-Router v4/v5 Prompt with custom modal component

React-Router v4 Prompt on Safari

The Problem

A Simple Solution

handleBlockedRoute = (nextLocation) => {
const {confirmedNavigation} = this.state
const {shouldBlockNavigation} = this.props
if (!confirmedNavigation && shouldBlockNavigation(nextLocation)){
this.showModal(nextLocation)
return false
}

return true
}
import React from 'react'
import {Prompt} from 'react-router-dom'
import {CustomModal} from './CustomModal'export class RouteLeavingGuard extends React.Component { state = {
modalVisible: false,
lastLocation: null,
confirmedNavigation: false,
}
showModal = (location) => this.setState({
modalVisible: true,
lastLocation: location,
})
closeModal = (callback) => this.setState({
modalVisible: false
}, callback)
handleBlockedNavigation = (nextLocation) => {
const {confirmedNavigation} = this.state
const {shouldBlockNavigation} = this.props
if (!confirmedNavigation && shouldBlockNavigation(nextLocation)){
this.showModal(nextLocation)
return false
}

return true
}
handleConfirmNavigationClick = () => this.closeModal(() => {
const {navigate} = this.props
const {lastLocation} = this.state
if (lastLocation) {
this.setState({
confirmedNavigation: true
}, () => {
// Navigate to the previous blocked location with your navigate function
navigate(lastLocation.pathname)
})
}
})
render() {
const {when} = this.props
const {modalVisible, lastLocation} = this.state
return (
<>
<Prompt
when={when}
message={this.handleBlockedNavigation}/>
<CustomModal
visible={modalVisible}
onCancel={this.closeModal}
onConfirm={this.handleConfirmNavigationClick}/>
</>
)
}
}export default RouteLeavingGuard
import React from ‘react’
import RouteLeavingGuard from 'components/RouteLeavingGuard'
export class LoginScene extends React.Component {
state = {
username: '',
password: '',
isDirty: false,
}
// ...Other scene helper functions render() {
const { isDirty } = this.state
const { history } = this.props
return (
<React.Fragment>
// ...Other scene components

<RouteLeavingGuard
// When should shouldBlockNavigation be invoked,
// simply passing a boolean
// (same as "when" prop of Prompt of React-Router)
when={isDirty}
// Navigate function
navigate={path => history.push(path)}
// Use as "message" prop of Prompt of React-Router
shouldBlockNavigation={location => {
// This case it blocks the navigation when:
// 1. the login form is dirty, and
// 2. the user is going to 'sign-up' scene.
// (Just an example, in real case you might
// need to block all location regarding this case)
if (isDirty) {
if (location.pathname === 'signup') {
return true
}
}
return false
}}
/>
</React.Fragment>
)
}
}
export default LoginScene
import { Location } from ‘history’;
import React, { useEffect, useState } from ‘react’;
import { Prompt } from ‘react-router-dom’;
import WarningDialog from ‘./WarningDialog’;
interface Props {
when?: boolean | undefined;
navigate: (path: string) => void;
shouldBlockNavigation: (location: Location) => boolean;
}
const RouteLeavingGuard = ({
when,
navigate,
shouldBlockNavigation,
}: Props) => {
const [modalVisible, setModalVisible] = useState(false);
const [lastLocation, setLastLocation] = useState<Location | null>(null);
const [confirmedNavigation, setConfirmedNavigation] = useState(false);
const closeModal = () => {
setModalVisible(false);
};
const handleBlockedNavigation = (nextLocation: Location): boolean => {
if (!confirmedNavigation && shouldBlockNavigation(nextLocation)) {
setModalVisible(true);
setLastLocation(nextLocation);
return false;
}
return true;
};
const handleConfirmNavigationClick = () => {
setModalVisible(false);
setConfirmedNavigation(true);
};
useEffect(() => {
if (confirmedNavigation && lastLocation) {
// Navigate to the previous blocked location with your navigate function
navigate(lastLocation.pathname);
}
}, [confirmedNavigation, lastLocation]);
return (
<>
<Prompt when={when} message={handleBlockedNavigation} />
{/* Your own alert/dialog/modal component */}
<WarningDialog
open={modalVisible}
titleText=”Close without saving?”
contentText=”You have unsaved changes. Are you sure you want to leave this page without saving?”
cancelButtonText=”DISMISS”
confirmButtonText=”CONFIRM”
onCancel={closeModal}
onConfirm={handleConfirmNavigationClick}
/>
</>
);
};
export default RouteLeavingGuard;

Based in Hong Kong, a passionate learner and perfectionist, who wants to build something cool and quality.