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.
// 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
1. Using the useLocation
Hook (Recommended)
The simplest and most reliable approach is to use the useLocation
hook with useEffect
:
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
:
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:
// 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:
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:
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
Use
useLocation
for most cases - It's the simplest and most reliable solution for tracking route changes.Clean up effects properly - Always return cleanup functions from your
useEffect
hooks to prevent memory leaks.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.
Avoid premature optimization - Start with the simplest solution (
useLocation
) and only use more complex approaches when necessary.
Common Use Cases
// 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:
- Replace
useHistory().listen()
withuseLocation()
in auseEffect
- For back/forward navigation detection, use
useNavigationType()
- 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.