66

I'm using Material-ui's Tabs, which are controlled and I'm using them for (React-router) Links like this:

    <Tab value={0} label="dashboard" containerElement={<Link to="/dashboard/home"/>}/>
    <Tab value={1} label="users" containerElement={<Link to="/dashboard/users"/>} />
  <Tab value={2} label="data" containerElement={<Link to="/dashboard/data"/>} />

If I'm currenlty visting dashboard/data and I click browser's back button I go (for example) to dashboard/users but the highlighted Tab still stays on dashboard/data (value=2)

I can change by setting state, but I don't know how to handle the event when the browser's back button is pressed?

I've found this:

window.onpopstate = this.onBackButtonEvent;

but this is called each time state is changed (not only on back button event)

high incompetance
  • 1,864
  • 3
  • 14
  • 23

13 Answers13

47

This is a bit old question and you've probably already got your answer, but for people like me who needed this, I'm leaving this answer.

Using react-router made the job simple as such:

import { browserHistory } from 'react-router';

componentDidMount() {
    super.componentDidMount();

    this.onScrollNearBottom(this.scrollToLoad);

    this.backListener = browserHistory.listen(location => {
      if (location.action === "POP") {
        // Do your stuff
      }
    });
  }

componentWillUnmount() {
    super.componentWillUnmount();
    // Unbind listener
    this.backListener();
}
Jay Shin
  • 724
  • 1
  • 6
  • 11
  • doesn't work for me. using react-router@3.0.5, location.action is always PUSH, even when clicking the browser back button – loopmode Aug 11 '17 at 10:22
  • @loopmode when you handle the back button action, to you use something like ```browserHistory.goBack()```?? – Jay Shin Aug 12 '17 at 11:28
  • 2
    I found that elsewhere, a colleague is meddling with the history manually, something along the lines if (nextLocation.action === 'POP' && getStepIndex(nextLocation.pathname) === 0) { browserHistory.push({pathname: `${getPathForIndex(0)}`}); return false; } so.. `if POP then PUSH` in order to make the forward button unavailable (which would go forward without a form being submitted) So.. Your answer remains correct - user error on my side :) – loopmode Aug 14 '17 at 05:14
  • 3
    As of react-router@5.0.0 you can't import `browserHistory` as illustrated in this response. It appears that `history` is included in the `props` passed to any component that is referenced from a route. Feel free to correct me if that's not quite right. – Stoph Jun 07 '19 at 00:35
  • I think it is the history.action === "POP" and also it happens with refresh too – Eran Or Oct 24 '19 at 16:31
  • 3
    For anyone using React Router 4+, then the listen has 2 parameters, location and action. `history.listen((loc, action) => if (action === 'POP') // do stuff)` – mawburn Jan 31 '20 at 22:01
  • how do i prevent the POP from being executed though? – Paulo Feb 04 '20 at 07:29
26

Using hooks you can detect the back and forward buttons

import { useHistory } from 'react-router-dom'


const [ locationKeys, setLocationKeys ] = useState([])
const history = useHistory()

useEffect(() => {
  return history.listen(location => {
    if (history.action === 'PUSH') {
      setLocationKeys([ location.key ])
    }

    if (history.action === 'POP') {
      if (locationKeys[1] === location.key) {
        setLocationKeys(([ _, ...keys ]) => keys)

        // Handle forward event

      } else {
        setLocationKeys((keys) => [ location.key, ...keys ])

        // Handle back event

      }
    }
  })
}, [ locationKeys, ])
Nicolas Keller
  • 413
  • 4
  • 7
  • 1
    What's the _ (underscore) in setLocationKeys(([ _, ...keys ]) => keys) – Shabbir Essaji Nov 25 '20 at 10:30
  • 3
    @ShabbirEssaji It uses [destructing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) and the spread operator, to return an array with the first element removed. [This may help](https://codeburst.io/a-simple-guide-to-destructuring-and-es6-spread-operator-e02212af5831) – rob-gordon Nov 25 '20 at 18:50
18

here is how I ended up doing it:

componentDidMount() {
    this._isMounted = true;
    window.onpopstate = ()=> {
      if(this._isMounted) {
        const { hash } = location;
        if(hash.indexOf('home')>-1 && this.state.value!==0)
          this.setState({value: 0})
        if(hash.indexOf('users')>-1 && this.state.value!==1)
          this.setState({value: 1})
        if(hash.indexOf('data')>-1 && this.state.value!==2)
          this.setState({value: 2})
      }
    }
  }

thanks everybody for helping lol

high incompetance
  • 1,864
  • 3
  • 14
  • 23
11

Hooks sample

const {history} = useRouter();
  useEffect(() => {
    return () => {
      // && history.location.pathname === "any specific path")
      if (history.action === "POP") {
        history.replace(history.location.pathname, /* the new state */);
      }
    };
  }, [history])

I don't use history.listen because it doesn't affect the state

const disposeListener = history.listen(navData => {
        if (navData.pathname === "/props") {
            navData.state = /* the new state */;
        }
    });
Ashish Yadav
  • 2,355
  • 1
  • 11
  • 21
Bnaya
  • 417
  • 4
  • 6
10

Version 3.x of the React Router API has a set of utilities you can use to expose a "Back" button event before the event registers with the browser's history. You must first wrap your component in the withRouter() higher-order component. You can then use the setRouteLeaveHook() function, which accepts any route object with a valid path property and a callback function.

import {Component} from 'react';
import {withRouter} from 'react-router';

class Foo extends Component {
  componentDidMount() {
    this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
  }

  routerWillLeave(nextState) { // return false to block navigation, true to allow
    if (nextState.action === 'POP') {
      // handle "Back" button clicks here
    }
  }
}

export default withRouter(Foo);
brogrammer
  • 696
  • 1
  • 10
  • 19
  • For me its just giving an error `TypeError: Cannot read property 'setRouteLeaveHook' of undefined` – Nikita Vlasenko Aug 04 '20 at 15:00
  • @NikitaVlasenko Expanding on the example above, `Foo` needs to be passed to a `` component, or at the very least needs to inherit a route component's props. (E.g., in your `routes.js` file, `/* ... */`) – brogrammer Aug 05 '20 at 04:39
5

I used withrouter hoc in order to get history prop and just write a componentDidMount() method:

componentDidMount() {
    if (this.props.history.action === "POP") {
        // custom back button implementation
    }
}
  • It is triggering before click back button, Can you help on this?, we need to trigger a custom popup after click back button. – 151291 Apr 02 '19 at 06:20
5

Most of the answers for this question either use outdated versions of React Router, rely on less-modern Class Components, or are confusing; and none use Typescript, which is a common combination. Here is an answer using Router v5, function components, and Typescript:

// use destructuring to access the history property of the ReactComponentProps type
function MyComponent( { history }: ReactComponentProps) {

    // use useEffect to access lifecycle methods, as componentDidMount etc. are not available on function components.
    useEffect(() => {

        return () => {
            if (history.action === "POP") {
                // Code here will run when back button fires. Note that it's after the `return` for useEffect's callback; code before the return will fire after the page mounts, code after when it is about to unmount.
                }
           }
    })
}

A fuller example with explanations can be found here.

Andy
  • 1,012
  • 12
  • 22
3

For giving warning on the press of browser back in react functional components. do the following steps

  1. declare isBackButtonClicked and initialize it as false and maintain the state using setBackbuttonPress function.
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
  1. In componentdidmount, add the following lines of code
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
  1. define onBackButtonEvent Function and write logic as per your requirement.

      const onBackButtonEvent = (e) => {
      e.preventDefault();
      if (!isBackButtonClicked) {
    
      if (window.confirm("Do you want to go to Test Listing")) {
        setBackbuttonPress(true)
        props.history.go(listingpage)
      } else {
        window.history.pushState(null, null, window.location.pathname);
        setBackbuttonPress(false)
      }
    }
    

    }

  2. In componentwillmount unsubscribe onBackButtonEvent Function

Final code will look like this

import React,{useEffect,useState} from 'react'

function HandleBrowserBackButton() {
  const [isBackButtonClicked, setBackbuttonPress] = useState(false)

  useEffect(() => {

    window.history.pushState(null, null, window.location.pathname);
    window.addEventListener('popstate', onBackButtonEvent);

    //logic for showing popup warning on page refresh
    window.onbeforeunload = function () {

      return "Data will be lost if you leave the page, are you sure?";
    };
    return () => {
      window.removeEventListener('popstate', onBackButtonEvent);
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  const onBackButtonEvent = (e) => {
    e.preventDefault();
    if (!isBackButtonClicked) {

      if (window.confirm("Do you want to go to Test Listing")) {
        setBackbuttonPress(true)
        props.history.go(listingpage)
      } else {
        window.history.pushState(null, null, window.location.pathname);
        setBackbuttonPress(false)
      }
    }
  }

  return (
    <div>

    </div>
  )
}

export default HandleBrowserBackButton
Jared Forth
  • 1,418
  • 5
  • 13
  • 28
1

It depends on the type of Router you use in React.

If you use BrowserRouter from react-router (not available in react-router v4 though), as mentioned above, you can use the action 'POP' to intercept the browser back button.

However, if you use HashRouter to push the routes, above solution will not work. The reason is hash router always triggered with 'POP' action when you click browser back button or pushing the route from your components. You cant differentiate these two actions either with window.popstate or history.listen simply.

vinzbruce
  • 11
  • 1
1

If you are using React Router V5, you can try 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>

<Prompt
   message={(location, action) => {
   if (action === 'POP') {
      console.log("Backing up...")
      // Add your back logic here
   }

   return true;
   }}
/>
drlingyi
  • 501
  • 1
  • 4
  • 11
0

Upcoming version 6.0 introduces useBlocker hook - which could be used to intercept all navigation attempts.

import { Action } from 'history';
import { useBlocker } from 'react-router';

// when blocker should be active
const unsavedChanges = true;

useBlocker((transition) => {
    const {
        location, // The new location
        action, // The action that triggered the change
    } = transition;

    // intercept back and forward actions:
    if (action === Action.Pop) {
        alert('intercepted!')
    }

}, unsavedChanges);
kajkal
  • 307
  • 3
  • 5
0

Using hooks. I have converted @Nicolas Keller's code to typescript

  const [locationKeys, setLocationKeys] = useState<(string | undefined)[]>([]);
  const history = useHistory();

  useEffect(() => {
    return history.listen((location) => {
      if (history.action === 'PUSH') {
        if (location.key) setLocationKeys([location.key]);
      }

      if (history.action === 'POP') {
        if (locationKeys[1] === location.key) {
          setLocationKeys(([_, ...keys]) => keys);

          // Handle forward event
          console.log('forward button');
        } else {
          setLocationKeys((keys) => [location.key, ...keys]);

          // Handle back event
          console.log('back button');
          removeTask();
        }
      }
    });
  }, [locationKeys]);
Ali Rehman
  • 2,573
  • 3
  • 19
  • 23
-4

You can use "withrouter" HOC and use this.props.history.goBack.

<Button onClick={this.props.history.goBack}>
    BACK
</Button>
Guillaume Raymond
  • 1,328
  • 15
  • 28
Aman Singh
  • 93
  • 1
  • 4
  • This illustrates how to create a back button, not intercept the browser's back button, as the OP requested. – Andy May 21 '20 at 16:51