Intercept/handle browser's back button in React-router?

166,269

Solution 1

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

Solution 2

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

Solution 3

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, ])

Solution 4

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 */;
        }
    });

Solution 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.

Share:
166,269

Related videos on Youtube

high incompetance
Author by

high incompetance

Updated on October 14, 2021

Comments

  • high incompetance
    high incompetance over 2 years

    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)

    • Hyzyr
      Hyzyr about 2 years
      I know it is late, but call it in useEffect with empty params like : useEffect(()=>{/* here */}, [])
  • loopmode
    loopmode almost 7 years
    doesn't work for me. using [email protected], location.action is always PUSH, even when clicking the browser back button
  • Jay Shin
    Jay Shin almost 7 years
    @loopmode when you handle the back button action, to you use something like browserHistory.goBack()??
  • loopmode
    loopmode almost 7 years
    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 :)
  • 151291
    151291 about 5 years
    It is triggering before click back button, Can you help on this?, we need to trigger a custom popup after click back button.
  • Stoph
    Stoph about 5 years
    As of [email protected] 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.
  • Atombit
    Atombit over 4 years
    it is not a react way
  • Eran Or
    Eran Or over 4 years
    I think it is the history.action === "POP" and also it happens with refresh too
  • anerco
    anerco over 4 years
    i would change the dependency array to [history.location, history.action] because it wont catch the location changes
  • mawburn
    mawburn over 4 years
    For anyone using React Router 4+, then the listen has 2 parameters, location and action. history.listen((loc, action) => if (action === 'POP') // do stuff)
  • Paulo
    Paulo over 4 years
    how do i prevent the POP from being executed though?
  • Andrew
    Andrew about 4 years
    Isn't useRouter() specific to the Next.js framework?
  • Andrew
    Andrew about 4 years
    This illustrates how to create a back button, not intercept the browser's back button, as the OP requested.
  • Nikita Vlasenko
    Nikita Vlasenko almost 4 years
    For me its just giving an error TypeError: Cannot read property 'setRouteLeaveHook' of undefined
  • brogrammer
    brogrammer almost 4 years
    @NikitaVlasenko Expanding on the example above, Foo needs to be passed to a <Route /> component, or at the very least needs to inherit a route component's props. (E.g., in your routes.js file, <Route component={Foo}>/* ... */</Route>)
  • mheavers
    mheavers almost 4 years
    I could only find a useRouter() function in the useHooks library: usehooks.com/useRouter
  • Shabbir Essaji
    Shabbir Essaji over 3 years
    What's the _ (underscore) in setLocationKeys(([ _, ...keys ]) => keys)
  • rob-gordon
    rob-gordon over 3 years
    @ShabbirEssaji It uses destructing and the spread operator, to return an array with the first element removed. This may help
  • Sam
    Sam over 3 years
    You should include the back logic
  • KshitijV97
    KshitijV97 about 3 years
    What does the _ do
  • Peter Moore
    Peter Moore almost 3 years
    The "react way" is overly restrictive and byzantine.
  • Josh J
    Josh J almost 3 years
    @ShabbirEssaji you probably found your answer by now, but an underscore is used a lot by convention when a variable must be assigned but won't be used.
  • Joseph Beuys' Mum
    Joseph Beuys' Mum over 2 years
    is this still a good way to handle both back and forward or has anything been added to deal with the arguably hacky locationKeys, setLocationKeys bit? yet. pls. ty
  • Nicolas Keller
    Nicolas Keller over 2 years
    So far yes, this is the best solution we have.
  • Michael Harley
    Michael Harley over 2 years
    @KshitijV97 That means "ignore this variable", additionally the convention of _ can be used like this _firstElement, and this _firstElement won't cause any warning/error even if it's not used.
  • Emaborsa
    Emaborsa over 2 years
    It'a a nice solution, but IMHO not what requested. The request is "How to intercept/handle the back button", but this code triggered even if a new url is set per code.
  • Sriram R
    Sriram R about 2 years
    Any hack while using hashRouter? Prompt is triggered after url changes in the browser