使用react-router检测用户离开页面

时间:2015-09-29 10:36:53

标签: reactjs react-router

我希望我的ReactJS应用程序在离开特定页面时通知用户。特别是弹出消息,提醒他/她做一个动作:

  

“更改已保存,但尚未发布。请立即执行此操作吗?”

我是否应该在react-router全局触发此问题,或者这是否可以在反应页/组件中完成?

我在后者身上找不到任何东西,我宁愿避开第一个。除非它当然是标准,但这让我想知道如何做这样的事情,而不必将代码添加到用户可以去的每个其他可能的页面..

欢迎任何见解,谢谢!

8 个答案:

答案 0 :(得分:59)

react-router v4引入了一种使用Prompt阻止导航的新方法。只需将其添加到您要阻止的组件:

import { Prompt } from 'react-router'

const MyComponent = () => (
  <React.Fragment>
    <Prompt
      when={shouldBlockNavigation}
      message='You have unsaved changes, are you sure you want to leave?'
    />
    {/* Component JSX */}
  </React.Fragment>
)

这将阻止任何路由,但不会阻止页面刷新或关闭。要阻止它,您需要添加它(根据需要使用适当的React生命周期进行更新):

componentDidUpdate = () => {
  if (shouldBlockNavigation) {
    window.onbeforeunload = () => true
  } else {
    window.onbeforeunload = undefined
  }
}

onbeforeunload得到浏览器的各种支持。

答案 1 :(得分:26)

在react-router v2.4.0或更高版本以及v4之前有几个选项

  1. Add function onLeave for Route
  2.  <Route
          path="/home"
          onEnter={ auth }
          onLeave={ showConfirm }
          component={ Home }
        >
    
    1. Use function setRouteLeaveHook for componentDidMount
    2. 您可以在离开带有假挂钩的路线之前阻止转换发生或提示用户。

      const Home = withRouter(
        React.createClass({
      
          componentDidMount() {
            this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
          },
      
          routerWillLeave(nextLocation) {
            // return false to prevent a transition w/o prompting the user,
            // or return a string to allow the user to decide:
            // return `null` or nothing to let other hooks to be executed
            //
            // NOTE: if you return true, other hooks will not be executed!
            if (!this.state.isSaved)
              return 'Your work is not saved! Are you sure you want to leave?'
          },
      
          // ...
      
        })
      )
      

      请注意,此示例使用withRouter中引入的v2.4.0.高阶组件

      然而,当手动更改URL中的路径时,这些解决方案并不完美。

      在某种意义上

      • 我们看到确认 - 确定
      • 包含页面不会重新加载 - 确定
      • 网址没有变化 - 不行

      react-router v4使用提示或自定义历史记录:

      但是在react-router v4中,在#strong> Prompt 的帮助下,反应路由器

      更容易实施

      根据文件

        

      <强>提示

           

      用于在离开页面之前提示用户。当你的   应用程序进入一个应该阻止用户的状态   导航(就像表格填写完整一样),渲染<Prompt>

      import { Prompt } from 'react-router'
      
      <Prompt
        when={formIsHalfFilledOut}
        message="Are you sure you want to leave?"
      />
      
           

      消息:字符串

           

      用于在用户尝试离开时提示用户的消息。

      <Prompt message="Are you sure you want to leave?"/>
      
           

      消息:func

           

      将使用用户所在的下一个位置和操作进行调用   试图导航到。返回一个字符串以显示提示   user或true表示允许转换。

      <Prompt message={location => (
        `Are you sure you want to go to ${location.pathname}?`
      )}/>
      
           

      时:bool

           

      而不是有条件地在守卫后面渲染<Prompt>   可以随时呈现它,但可以将when={true}when={false}传递给   相应地阻止或允许导航。

      在您的渲染方法中,您只需根据需要添加文档中提到的内容。

      <强>更新

      如果您希望在使用离开页面时执行自定义操作,则可以使用自定义历史记录并配置路由器

      <强> history.js

      import createBrowserHistory from 'history/createBrowserHistory'
      export const history = createBrowserHistory()
      
      ... 
      import { history } from 'path/to/history';
      <Router history={history}>
        <App/>
      </Router>
      

      然后在您的组件中,您可以使用history.block之类的

      import { history } from 'path/to/history';
      class MyComponent extends React.Component {
         componentDidMount() {
            this.unblock = history.block(targetLocation => {
                 // take your action here     
                 return false;
            });
         }
         componentWillUnmount() {
            this.unblock();
         }
         render() {
            //component render here
         }
      }
      

答案 2 :(得分:23)

适用于react-router 2.4.0 +

注意:建议您将所有代码迁移到最新的react-router以获取所有新商品。

根据react-router documentation

的建议

应该使用withRouter高阶组件:

  

我们认为这款新的HoC更好更容易,并将在其中使用   文档和示例,但并不是一个很难的要求   开关。

作为文档中的ES6示例:

import React from 'react'
import { withRouter } from 'react-router'

const Page = React.createClass({

  componentDidMount() {
    this.props.router.setRouteLeaveHook(this.props.route, () => {
      if (this.state.unsaved)
        return 'You have unsaved information, are you sure you want to leave this page?'
    })
  }

  render() {
    return <div>Stuff</div>
  }

})

export default withRouter(Page)

答案 3 :(得分:3)

适用于react-router v3.x

我遇到了同样的问题,我需要在页面上进行任何未保存的更改的确认消息。在我的情况下,我使用 React Router v3 ,因此我无法使用从{strong> React Router v4 引入的<Prompt />

我处理后退按钮点击&#39;和&#39;意外链接点击&#39;结合使用setRouteLeaveHookhistory.pushState(),并处理重新加载按钮&#39;使用onbeforeunload事件处理程序。

setRouteLeaveHook doc)&amp; history.pushState doc

  • 仅使用setRouteLeaveHook是不够的。出于某种原因,虽然页面保持不变,但是在“后退按钮”时,网址已更改。点击了。

      // setRouteLeaveHook returns the unregister method
      this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
        this.props.route,
        this.routerWillLeave
      );
    
      ...
    
      routerWillLeave = nextLocation => {
        // Using native 'confirm' method to show confirmation message
        const result = confirm('Unsaved work will be lost');
        if (result) {
          // navigation confirmed
          return true;
        } else {
          // navigation canceled, pushing the previous path
          window.history.pushState(null, null, this.props.route.path);
          return false;
        }
      };
    

onbeforeunload doc

  • 它用于处理意外重新加载&#39;按钮

    window.onbeforeunload = this.handleOnBeforeUnload;
    
    ...
    
    handleOnBeforeUnload = e => {
      const message = 'Are you sure?';
      e.returnValue = message;
      return message;
    }
    

以下是我撰写的完整组件

  • 请注意,withRouter用于this.props.router
  • 请注意this.props.route从调用组件传递下来
  • 请注意currentState作为prop传递给初始状态并检查任何更改

    import React from 'react';
    import PropTypes from 'prop-types';
    import _ from 'lodash';
    import { withRouter } from 'react-router';
    import Component from '../Component';
    import styles from './PreventRouteChange.css';
    
    class PreventRouteChange extends Component {
      constructor(props) {
        super(props);
        this.state = {
          // initialize the initial state to check any change
          initialState: _.cloneDeep(props.currentState),
          hookMounted: false
        };
      }
    
      componentDidUpdate() {
    
       // I used the library called 'lodash'
       // but you can use your own way to check any unsaved changed
        const unsaved = !_.isEqual(
          this.state.initialState,
          this.props.currentState
        );
    
        if (!unsaved && this.state.hookMounted) {
          // unregister hooks
          this.setState({ hookMounted: false });
          this.unregisterRouteHook();
          window.onbeforeunload = null;
        } else if (unsaved && !this.state.hookMounted) {
          // register hooks
          this.setState({ hookMounted: true });
          this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
            this.props.route,
            this.routerWillLeave
          );
          window.onbeforeunload = this.handleOnBeforeUnload;
        }
      }
    
      componentWillUnmount() {
        // unregister onbeforeunload event handler
        window.onbeforeunload = null;
      }
    
      handleOnBeforeUnload = e => {
        const message = 'Are you sure?';
        e.returnValue = message;
        return message;
      };
    
      routerWillLeave = nextLocation => {
        const result = confirm('Unsaved work will be lost');
        if (result) {
          return true;
        } else {
          window.history.pushState(null, null, this.props.route.path);
          if (this.formStartEle) {
            this.moveTo.move(this.formStartEle);
          }
          return false;
        }
      };
    
      render() {
        return (
          <div>
            {this.props.children}
          </div>
        );
      }
    }
    
    PreventRouteChange.propTypes = propTypes;
    
    export default withRouter(PreventRouteChange);
    

如果有任何问题,请告诉我:)

答案 4 :(得分:2)

react-router v0.13.x与react v0.13.x:

使用willTransitionTo()willTransitionFrom()静态方法可以实现这一点。对于较新版本,请参阅下面的其他答案。

来自react-router documentation

  

您可以在路由处理程序中定义一些静态方法,这些方法将在路由转换期间调用。

     

willTransitionTo(transition, params, query, callback)

     

当处理程序即将渲染时调用,使您有机会中止或重定向转换。您可以在执行某些异步工作时暂停转换,并在完成后调用回调(错误),或者在您的参数列表中省略回调,它将为您调用。

     

willTransitionFrom(transition, component, callback)

     

在转换活动路线时调用,为您提供中止转换的机会。组件是当前组件,您可能需要它来检查其状态以决定是否允许转换(如表单字段)。

     

实施例

  var Settings = React.createClass({
    statics: {
      willTransitionTo: function (transition, params, query, callback) {
        auth.isLoggedIn((isLoggedIn) => {
          transition.abort();
          callback();
        });
      },

      willTransitionFrom: function (transition, component) {
        if (component.formHasUnsavedData()) {
          if (!confirm('You have unsaved information,'+
                       'are you sure you want to leave this page?')) {
            transition.abort();
          }
        }
      }
    }

    //...
  });

对于react-router 1.0.0-rc1 react v0.14.x或更高版本:

这应该可以使用routerWillLeave生命周期钩子。对于旧版本,请参阅上面的答案。

来自react-router documentation

  

要安装此挂钩,请在其中一个路由组件中使用Lifecycle mixin。

  import { Lifecycle } from 'react-router'

  const Home = React.createClass({

    // Assuming Home is a route component, it may use the
    // Lifecycle mixin to get a routerWillLeave method.
    mixins: [ Lifecycle ],

    routerWillLeave(nextLocation) {
      if (!this.state.isSaved)
        return 'Your work is not saved! Are you sure you want to leave?'
    },

    // ...

  })

的东西。可能会在最终版本发布之前发生变化。

答案 5 :(得分:1)

您可以使用此提示。

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Prompt } from "react-router-dom";

function PreventingTransitionsExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Form</Link>
          </li>
          <li>
            <Link to="/one">One</Link>
          </li>
          <li>
            <Link to="/two">Two</Link>
          </li>
        </ul>
        <Route path="/" exact component={Form} />
        <Route path="/one" render={() => <h3>One</h3>} />
        <Route path="/two" render={() => <h3>Two</h3>} />
      </div>
    </Router>
  );
}

class Form extends Component {
  state = { isBlocking: false };

  render() {
    let { isBlocking } = this.state;

    return (
      <form
        onSubmit={event => {
          event.preventDefault();
          event.target.reset();
          this.setState({
            isBlocking: false
          });
        }}
      >
        <Prompt
          when={isBlocking}
          message={location =>
            `Are you sure you want to go to ${location.pathname}`
          }
        />

        <p>
          Blocking?{" "}
          {isBlocking ? "Yes, click a link or the back button" : "Nope"}
        </p>

        <p>
          <input
            size="50"
            placeholder="type something to block transitions"
            onChange={event => {
              this.setState({
                isBlocking: event.target.value.length > 0
              });
            }}
          />
        </p>

        <p>
          <button>Submit to stop blocking</button>
        </p>
      </form>
    );
  }
}

export default PreventingTransitionsExample;

答案 6 :(得分:0)

使用history.listen

例如如下所示:

在您的组件中,

  componentWillMount() {
    this.props.history.listen(() => {
      // Detecting, user has changed URL
      console.info(this.props.history.location.pathname);
    });
  }

答案 7 :(得分:0)

也许您可以使用 componentWillUnmount() 在用户离开页面之前执行任何操作。如果您正在使用功能组件,那么您可以使用 useEffect() 钩子做同样的事情。钩子接受一个返回 Destructor 的函数,这类似于 componentWillUnmount() 可以做的。

信用转到this article