Although you can make use of a custom Modal component while preventing navigating between pages through Links, you can't show a custom modal while trying to close browser or reload it.
However if thats fine with you, you can make use of history.listen
to and block navigation. I wrote a generic HOC for it which solves this use case.
In the below code whitelisted pathnames are the pathnames that you would want the other person to navigate to without showing the prompt
import React from 'react';
import { withRouter } from 'react-router';
import _ from 'lodash';
const navigationPromptFactory = ({ Prompt }) => {
const initialState = {
currentLocation: null,
targetLocation: null,
isOpen: false
};
class NavigationPrompt extends React.Component {
static defaultProps = {
when: true
};
state = initialState;
componentDidMount() {
this.block(this.props);
window.addEventListener('beforeunload', this.onBeforeUnload);
}
componentWillReceiveProps(nextProps) {
const {
when: nextWhen,
history: nextHistory,
whiteListedPathnames: nextWhiteListedPaths
} = nextProps;
const { when, history, whiteListedPathnames } = this.props;
if (
when !== nextWhen ||
!_.isEqual(nextHistory.location, history.location) ||
!_.isEqual(whiteListedPathnames, nextWhiteListedPaths)
) {
this.unblock();
this.block(nextProps);
}
}
componentWillUnmount() {
this.unblock();
window.removeEventListener('beforeunload', this.onBeforeUnload);
}
onBeforeUnload = e => {
const { when } = this.props;
// we can't override an onBeforeUnload dialog
// eslint-disable-next-line
// https://stackoverflow.com/questions/276660/how-can-i-override-the-onbeforeunload-dialog-and-replace-it-with-my-own
if (when) {
// support for custom message is no longer there
// https://www.chromestatus.com/feature/5349061406228480
// eslint-disable-next-line
// https://stackoverflow.com/questions/38879742/is-it-possible-to-display-a-custom-message-in-the-beforeunload-popup
// setting e.returnValue = "false" to show prompt, reference below
//https://github.com/electron/electron/issues/2481
e.returnValue = 'false';
}
};
block = props => {
const {
history,
when,
whiteListedPathnames = [],
searchQueryCheck = false
} = props;
this.unblock = history.block(targetLocation => {
const hasPathnameChanged =
history.location.pathname !== targetLocation.pathname;
const hasSearchQueryChanged =
history.location.search !== targetLocation.search;
const hasUrlChanged = searchQueryCheck
? hasPathnameChanged || hasSearchQueryChanged
: hasPathnameChanged;
const isTargetWhiteListed = whiteListedPathnames.includes(
targetLocation.pathname
);
const hasChanged =
when && hasUrlChanged && !isTargetWhiteListed;
if (hasChanged) {
this.setState({
currentLocation: history.location,
targetLocation,
isOpen: true
});
}
return !hasChanged;
});
};
onConfirm = () => {
const { history } = this.props;
const { currentLocation, targetLocation } = this.state;
this.unblock();
// replacing current location and then pushing navigates to the target otherwise not
// this is needed when the user tries to change the url manually
history.replace(currentLocation);
history.push(targetLocation);
this.setState(initialState);
};
onCancel = () => {
const { currentLocation } = this.state;
this.setState(initialState);
// Replacing the current location in case the user tried to change the url manually
this.unblock();
this.props.history.replace(currentLocation);
this.block(this.props);
};
render() {
return (
<Prompt
{...this.props}
isOpen={this.state.isOpen}
onCancel={this.onCancel}
onConfirm={this.onConfirm}
/>
);
}
}
return withRouter(NavigationPrompt);
};
export { navigationPromptFactory };
In order to use the above, you can simply provide your custom Prompt Modal like
const NavigationPrompt = navigationPromptFactory({
Prompt: AlertDialog
});
const whiteListedPathnames = [`${match.url}/abc`, match.url];
<NavigationPrompt
when={isEditingPlan}
cancelLabel={'Stay'}
confirmLabel={'Leave'}
whiteListedPathnames={whiteListedPathnames}
title={'Leave This Page'}
>
<span>
Unsaved Changes may not be saved
</span>
</NavigationPrompt>
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…