ReactRouter v4提示-覆盖默认警报

时间:2018-09-21 05:34:24

标签: javascript reactjs react-router

React Router v4 <Prompt></Prompt> component非常适合用于保护导航免于部分填写的表单的使用案例。

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

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

3 个答案:

答案 0 :(得分:6)

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

但是,如果您满意,您可以使用history.listen来阻止导航。我为此写了一个通用的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>

答案 1 :(得分:1)

默认情况下,提示组件不允许覆盖window.alert()的使用。

以下是与您的需求非常相似的对话的链接:

https://github.com/ReactTraining/react-router/issues/4635

您可以参考其中的几个关键点,大部分只是使用提示而不是使用提示,而是可以使自己的模式在特定用户操作上触发。 :)

希望这会有所帮助

答案 2 :(得分:0)

这里是一个使用钩子实现块功能的组件,组件对我不起作用,因为我想忽略对位置的搜索。

import { useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';

interface IProps {
    when: boolean;
    message: string;
}

export default function RouteLeavingGuard({ when, message }: IProps) {

    const history = useHistory();
    const lastPathName = useRef(history.location.pathname);

    useEffect(() => {

        const unlisten = history.listen(({ pathname }) => lastPathName.current = pathname);

        const unblock = history.block(({ pathname }) => {
            if (lastPathName.current !== pathname && when) {
                return message;
            }
        });

        return () => {
            unlisten();
            unblock();
        }
    }, [history, when, message]);

    return null;

}