Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
396 views
in Technique[技术] by (71.8m points)

javascript - ReactRouter v4 Prompt - override default alert

The React Router v4 <Prompt></Prompt> component is perfect for the use case of protecting navigation away from a partially filled out form.

But what if we want to supply our own logic in place of the default browser alert() that this component uses? React is intended for creating UIs, so it seems like a pretty reasonable use case. Digging through the issues on Prompt in the github I did not find anyone asking about this.

Does anyone know of a solution for providing custom behavior for the alert?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...