13

I need to detect if a route change has occurred so that I can change a variable to true.

I've looked through these questions:
1. https://github.com/ReactTraining/react-router/issues/3554
2. How to listen to route changes in react router v4?
3. Detect Route Change with react-router

None of them have worked for me. Is there a clear way to call a function when a route change occurs.

HunterLiu
  • 591
  • 3
  • 5
  • 18
  • Can you please elaborate on your use case? Maybe there is another solution instead of listening for route changes? – sn42 Jul 21 '18 at 20:57
  • I'm using socket.io, but have multiple pages. The disconnect function socket.io provides cannot tell the difference between route changes and a user leaving the entire site. If I can tell there's not a route change, then I know the user is leaving the site when using window.onbeforeunload. @sn42 – HunterLiu Jul 21 '18 at 21:13

6 Answers6

22

One way is to use the withRouter higher-order component.

Live demo (click the hyperlinks to change routes and view the results in the displayed console)

You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.

https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md

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

class App extends Component {
    componentDidUpdate(prevProps) {
        if (this.props.location.pathname !== prevProps.location.pathname) {
            console.log('Route change!');
        }
    }
    render() {
        return (
            <div className="App">
                ...routes
            </div>
        );
    }
}

export default withRouter(props => <App {...props}/>);

Another example that uses url params:

If you were changing profile routes from /profile/20 to /profile/32

And your route was defined as /profile/:userId

componentDidUpdate(prevProps) {
    if (this.props.match.params.userId !== prevProps.match.params.userId) {
        console.log('Route change!');
    }
}
wdm
  • 6,659
  • 1
  • 22
  • 27
  • My route changes: from /profile to /conversations or /tour to /matches. @wdm – HunterLiu Jul 21 '18 at 20:52
  • @Hunter690 Do you need to know specifically which route you've changed to or just that the route has changed? – wdm Jul 21 '18 at 21:10
  • just that my route changed – HunterLiu Jul 21 '18 at 21:11
  • @Hunter690 see edits I added another example using `withRouter` – wdm Jul 21 '18 at 21:31
  • I tried using withRouter, but I can't get componentDidUpdate to run for some reason (I added a console.log and didn't get anything on the console) – HunterLiu Jul 21 '18 at 21:34
  • @Hunter690 Check out this live example. Click the links in the render window to see the console on route change: https://codesandbox.io/embed/2jonpprp6y?expanddevtools=1&module=%2Fcomponents%2FApp.js – wdm Jul 21 '18 at 21:53
16

With React Hooks, it should be as simple as:

useEffect(() => {
    const { pathname } = location;
    console.log('New path:', pathname);
}, [location.pathname]);

By passing location.pathname in the second array argument, means you are saying to useEffect to only re-run if location.pathname changes.

Live example with code source: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i

parse
  • 989
  • 8
  • 19
  • 1
    This does not work for me. The callback is only called once, to show the initial path. But after pathname changes again, the new path is not output in tthe console. – jhuesos Sep 08 '19 at 09:47
  • I just made a simple React app on Codesandbox, it's working like charm, please check this out: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i – parse Sep 08 '19 at 19:55
  • I tried the same code and it didn't work in my app... I don't know... Thanks. I will upvote :) – jhuesos Sep 09 '19 at 06:25
  • Thanks, you can share with me you code so I could help :) – parse Sep 09 '19 at 13:26
  • I decided to no longer use react-router and I switched to [hookrouter](https://www.npmjs.com/package/hookrouter) – jhuesos Sep 10 '19 at 14:02
  • cool, that's should be the right path if you are starting a new project, but for big and mid sized projects, it's a bit hard to refactor the whole thing to hookrouter. good luck – parse Sep 11 '19 at 16:24
  • 1
    What if ```hash``` or ```search``` changes? – Niyas Nazar Apr 13 '20 at 06:56
  • It doesn't matter, we just tell useEffect to re-run if the `pathname` changes. – parse Apr 29 '20 at 10:16
6

React Router v5 now detects the route changes automatically thanks to hooks. Here's the example from the team behind it:

import { Switch, useLocation } from 'react-router'

function usePageViews() {
  let location = useLocation()

  useEffect(
    () => {
      ga.send(['pageview', location.pathname])
    },
    [location]
  )
}

function App() {
  usePageViews()
  return <Switch>{/* your routes here */}</Switch>
}

This example sends a "page view" to Google Analytics (ga) every time the URL changes.

Thanh-Quy Nguyen
  • 1,777
  • 3
  • 18
  • 35
  • 4
    The problem is that it updates on the same route because the location.key changes every time. If anything you need to listen for location.pathname in your effect – Daniel Harrison Oct 03 '19 at 07:45
2

When component is specified as <Route>'s component property, React Router 4 (RR4) passes to it few additional properties: match, location and history.

Then u should use componentDidUpdate lifecycle method to compare location objects before and after update (remember ES object comparison rules). Since location objects are immutable, they will never match. Even if u navigate to the same location.

  componentDidUpdate(newProps) {
    if (this.props.location !== newProps.location) {
      this.handleNavigation();
    }
  }

withRouter should be used when you need to access these properties within an arbitrary component that is not specified as a component property of any Route. Make sure to wrap your app in <BrowserRouter> since it provides all the necessary API, otherwise these methods will only work in components contained within <BrowserRouter>.

There are cases when user decides to reload the page via navigation buttons instead of dedicated interface in browsers. But comparisons like this:

this.props.location.pathname !== prevProps.location.pathname

will make it impossible.

0

How about tracking the length of the history object in your application state? The history object provided by react-router increases in length each time a new route is traversed. See image below.

enter image description here

ComponentDidMount and ComponentWillUnMount check: enter image description here

  • I tried to do that, but I have to know if its a route change before the next page loads since I'm using window.onbeforeunload. – HunterLiu Jul 21 '18 at 21:27
  • You can check the whether or not the length increases in `componentWillUnmount`. You can initialize the length in componentDidMount and check if you are going to another route in componentWillUnmount because the length will increase before mounting new component ... see second screenshot. Hope it works! – Cesar Napoleon Mejia Leiva Jul 21 '18 at 21:34
  • interesting idea; I tried to just print out `history` in a `componentDidMount` and `componentWillUnmount` and nothing came out the console. I have another question; in the second screenshot, there's a `Navigated to ...` print out; do you know how to access that in the code? – HunterLiu Jul 21 '18 at 21:43
  • I'm not sure if you can access that from the code. It's part of Chrome Dev Tools: https://stackoverflow.com/questions/28349285/chrome-console-shows-me-navigated-to-http-localhost – Cesar Napoleon Mejia Leiva Jul 21 '18 at 21:48
  • Do your route components have `history` on props? Verify using React Dev Tools to see what props are on . If history is not on props, maybe switch from `Route path='tour' component={Tour}` to `Route path='tour' render={(props) => ` to ensure the history object is available on your route components. – Cesar Napoleon Mejia Leiva Jul 21 '18 at 21:56
0

React use Component-Based Architecture. So, why don't we obey this rule?

You can see DEMO.

Each page must be wrapped by an HOC, this will detect changing of page automatically.

Home

import React from "react";
import { NavLink } from "react-router-dom";
import withBase from "./withBase";

const Home = () => (
  <div>
    <p>Welcome Home!!!</p>
    <NavLink to="/login">Go to login page</NavLink>
  </div>
);

export default withBase(Home);

withBase HOC

import React from "react";

export default WrappedComponent =>
  class extends React.Component {
    componentDidMount() {
      this.props.handleChangePage();
    }

    render() {
      return <WrappedComponent />;
    }
  };
Ukasha
  • 1,486
  • 2
  • 16
  • 29