小编典典

ReactRouter v4提示-覆盖默认警报

reactjs

React Router v4 <Prompt></Prompt> 组件非常适合用于保护导航免受部分填写的表单影响的用例。

但是,如果我们想提供自己的逻辑来代替alert()该组件使用的默认浏览器,该怎么办?React是用于创建UI的,因此这似乎是一个非常合理的用例。在github上浏览Prompt上的问题,我没有发现有人问这个问题。

有谁知道提供警报自定义行为的解决方案?


阅读 280

收藏
2020-07-22

共1个答案

小编典典

尽管可以在阻止通过链接在页面之间导航的同时使用自定义模态组件,但是 在尝试关闭浏览器或重新加载浏览器时无法显示自定义模态。

但是,如果您还可以,则可以使用history.listento并阻止导航。我为此编写了一个通用的HOC来解决此用例。

在下面的代码中,白名单中的路径名是您希望其他人导航到而不显示提示的路径名

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 };

为了使用以上内容,您可以简单地提供自定义的提示模式,例如

      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>
2020-07-22