Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting user leaving page with react-router

People also ask

How do I stop people from leaving my page react?

May be you can use componentWillUnmount() to do anything before the user leaving the page. If you are using functional components, then you can just do the same with useEffect() hook.

Why we use BrowserRouter in react?

React Router is a standard library for routing in React. It enables navigation between views from different components in a React application, allows the browser URL to be changed, and keeps the UI in sync with the URL.

What is the best way to redirect a page using react router?

To redirect to another page on button click in React: Use the useNavigate() hook, e.g. const navigate = useNavigate(); . Call the navigate() function, passing it the path - navigate('/about') .


react-router v4 introduces a new way to block navigation using Prompt. Just add this to the component that you would like to block:

import { Prompt } from 'react-router'

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

This will block any routing, but not page refresh or closing. To block that, you'll need to add this (updating as needed with the appropriate React lifecycle):

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

onbeforeunload has various support by browsers.


In react-router v2.4.0 or above and before v4 there are several options

  1. Add function onLeave for Route
 <Route
      path="/home"
      onEnter={ auth }
      onLeave={ showConfirm }
      component={ Home }
    >
    
  1. Use function setRouteLeaveHook for componentDidMount

You can prevent a transition from happening or prompt the user before leaving a route with a leave hook.

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?'
    },

    // ...

  })
)

Note that this example makes use of the withRouter higher-order component introduced in v2.4.0.

However these solution doesn't quite work perfectly when changing the route in URL manually

In the sense that

  • we see the Confirmation - ok
  • contain of page doesn't reload - ok
  • URL doesn't changes - not okay

For react-router v4 using Prompt or custom history:

However in react-router v4 , its rather easier to implement with the help of Prompt from'react-router

According to the documentation

Prompt

Used to prompt the user before navigating away from a page. When your application enters a state that should prevent the user from navigating away (like a form is half-filled out), render a <Prompt>.

import { Prompt } from 'react-router'

<Prompt
  when={formIsHalfFilledOut}
  message="Are you sure you want to leave?"
/>

message: string

The message to prompt the user with when they try to navigate away.

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

message: func

Will be called with the next location and action the user is attempting to navigate to. Return a string to show a prompt to the user or true to allow the transition.

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

when: bool

Instead of conditionally rendering a <Prompt> behind a guard, you can always render it but pass when={true} or when={false} to prevent or allow navigation accordingly.

In your render method you simply need to add this as mentioned in the documentation according to your need.

UPDATE:

In case you would want to have a custom action to take when user is leaving page, you can make use of custom history and configure your Router like

history.js

import createBrowserHistory from 'history/createBrowserHistory'
export const history = createBrowserHistory()

... 
import { history } from 'path/to/history';
<Router history={history}>
  <App/>
</Router>

and then in your component you can make use of history.block like

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

For react-router 2.4.0+

NOTE: It is advisable to migrate all your code to the latest react-router to get all the new goodies.

As recommended in the react-router documentation:

One should use the withRouter higher order component:

We think this new HoC is nicer and easier, and will be using it in documentation and examples, but it is not a hard requirement to switch.

As an ES6 example from the documentation:

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)

For react-router v3.x

I had the same issue where I needed a confirmation message for any unsaved change on the page. In my case, I was using React Router v3, so I could not use <Prompt />, which was introduced from React Router v4.

I handled 'back button click' and 'accidental link click' with the combination of setRouteLeaveHook and history.pushState(), and handled 'reload button' with onbeforeunload event handler.

setRouteLeaveHook (doc) & history.pushState (doc)

  • Using only setRouteLeaveHook was not enough. For some reason, the URL was changed although the page remained the same when 'back button' was clicked.

      // 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)

  • It is used to handle 'accidental reload' button

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

Below is the full component that I have written

  • note that withRouter is used to have this.props.router.
  • note that this.props.route is passed down from the calling component
  • note that currentState is passed as prop to have initial state and to check any change

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

Please let me know if there is any question :)