【问题标题】:ReactRouter v4 Prompt - override default alertReact Router v4 Prompt - 覆盖默认警报
【发布时间】:2019-02-25 11:28:22
【问题描述】:

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

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

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

【问题讨论】:

    标签: javascript reactjs react-router


    【解决方案1】:

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

    但是,如果您觉得没问题,您可以使用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>
    

    【讨论】:

      【解决方案2】:

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

      这里有一个与您的需求非常相似的对话链接:

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

      您可以参考其中的一些关键点,主要是您可以制作自己的模式以在特定用户操作上触发,而不是使用提示。 :)

      希望对你有帮助

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          猜你喜欢
          • 2018-06-13
          • 2010-12-16
          • 1970-01-01
          • 2011-12-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-08
          相关资源
          最近更新 更多