Skip to content

Listening for Route Changes in React Router v6

Problem

In React Router v5, you could listen for route changes using the history.listen() method from the useHistory hook. However, in v6, useHistory was replaced with useNavigate, which returns a function rather than an object and doesn't provide a listen method.

jsx
// React Router v5
import { useHistory } from "react-router-dom";

const history = useHistory();
history.listen((location, action) => {
  // Handle route changes
});

// React Router v6
import { useNavigate } from "react-router-dom";

const navigate = useNavigate();
navigate.listen(...); // Error: listen is not a function

Solutions

The simplest and most reliable approach is to use the useLocation hook with useEffect:

jsx
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

function MyComponent() {
  const location = useLocation();

  useEffect(() => {
    // This will run whenever the route changes
    console.log("Route changed to:", location.pathname);
    
    // You can perform any side effects here:
    // - Analytics tracking
    // - Scroll to top
    // - Reset component state
    // - etc.
  }, [location]); // location as dependency

  return (
    // Your component JSX
  );
}

WARNING

Be cautious when using this approach if your effect might trigger navigation that unmounts the component. Ensure cleanup is properly handled to avoid memory leaks.

2. Using useNavigationType for Specific Actions

If you need to detect specific navigation types (like browser back/forward buttons), use useNavigationType:

jsx
import { useEffect } from "react";
import {
  useLocation,
  useNavigationType,
  NavigationType
} from "react-router-dom";

function NavigationListener() {
  const location = useLocation();
  const navigationType = useNavigationType();

  useEffect(() => {
    if (navigationType === NavigationType.Pop) {
      // Back/forward navigation occurred
      console.log("Back/forward navigation to:", location.pathname);
    } else if (navigationType === NavigationType.Push) {
      // Programmatic navigation occurred
      console.log("Programmatic navigation to:", location.pathname);
    }
  }, [location, navigationType]);

  return null; // Or your component JSX
}

3. Custom History Object (Advanced)

For more control, you can create a custom history object and router:

jsx
// history.js
import { createBrowserHistory } from "history";
export default createBrowserHistory();

// CustomRouter.jsx
import { useState, useLayoutEffect } from "react";
import { Router } from "react-router-dom";

export const CustomRouter = ({ history, ...props }) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location
  });

  useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      {...props}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
};

// App.jsx
import { CustomRouter } from "./CustomRouter";
import history from "./history";

function App() {
  return (
    <CustomRouter history={history}>
      {/* Your app routes */}
    </CustomRouter>
  );
}

// AnyComponent.jsx
import { useEffect } from "react";
import history from "./history";

function AnyComponent() {
  useEffect(() => {
    const unlisten = history.listen(({ location, action }) => {
      console.log(`Route changed to ${location.pathname} via ${action}`);
    });
    
    return unlisten; // Cleanup on unmount
  }, []);
}

INFO

React Router v6.4+ includes an unstable_HistoryRouter that can be used instead of creating a custom router:

jsx
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import history from "./history";

<HistoryRouter history={history}>
  {/* Your app routes */}
</HistoryRouter>

Note that this API is marked as "unstable" and may change in future versions.

4. Data Router Subscription (v6.4+)

If you're using the new data routers in v6.4+, you can subscribe to state changes:

jsx
import { createBrowserRouter } from "react-router-dom";

const router = createBrowserRouter([
  // your routes
]);

// Subscribe to router state changes
router.subscribe((state) => {
  console.log("Router state changed:", state);
});

// Navigate programmatically
router.navigate("/path");
router.navigate("/path", { replace: true });

Best Practices

  1. Use useLocation for most cases - It's the simplest and most reliable solution for tracking route changes.

  2. Clean up effects properly - Always return cleanup functions from your useEffect hooks to prevent memory leaks.

  3. Be mindful of component unmounting - If your route change might unmount the listening component, consider using a global solution or moving the listener higher up the component tree.

  4. Avoid premature optimization - Start with the simplest solution (useLocation) and only use more complex approaches when necessary.

Common Use Cases

jsx
// Analytics tracking
useEffect(() => {
  ga('send', 'pageview', location.pathname);
}, [location]);

// Scroll to top on route change
useEffect(() => {
  window.scrollTo(0, 0);
}, [location]);

// Reset component state on route change
useEffect(() => {
  setFormData(initialState);
}, [location.pathname]);

Migration Tips

When migrating from v5 to v6:

  1. Replace useHistory().listen() with useLocation() in a useEffect
  2. For back/forward navigation detection, use useNavigationType()
  3. For advanced use cases, consider the custom history approach

The useLocation approach is the recommended solution for most applications as it leverages React Router's built-in hooks and follows React's component lifecycle patterns.