Optimizing useLocation Hook in React
The useLocation hook in React Router is essential for
accessing the current URL’s pathname, search parameters, and state.
However, because it returns a new location object on every route change,
it can trigger unnecessary component re-renders that degrade application
performance. This article explains why these performance bottlenecks
occur and provides actionable, straight-to-the-point strategies to
optimize the usage of useLocation in your React
applications.
The Re-render Problem with useLocation
Whenever a user navigates to a new route, React Router updates its
internal context. Any component containing the useLocation
hook will subscribe to these changes and re-render.
If a heavy component uses useLocation just to read a
single property (like checking if the user is on the
/dashboard path), that entire component and its children
will re-render on every single URL change—even if the path didn’t change
(for example, if only a query parameter or hash changed).
Strategy 1: Pass Primitives to Memoized Child Components
To prevent entire component trees from re-rendering, extract the
specific property you need from useLocation and pass it as
a primitive prop to a memoized child component.
import React, { memo } from 'react';
import { useLocation } from 'react-router-dom';
// This child component only re-renders if the pathname actually changes
const NavigationMenu = memo(({ pathname }) => {
return (
<nav>
<li className={pathname === '/' ? 'active' : ''}>Home</li>
<li className={pathname === '/about' ? 'active' : ''}>About</li>
</nav>
);
});
export default function AppHeader() {
const { pathname } = useLocation(); // Re-runs on any route change
return (
<header>
<h1>My App</h1>
{/* Only the primitive string is passed, enabling React.memo to work effectively */}
<NavigationMenu pathname={pathname} />
</header>
);
}Strategy 2: Use Specific Hooks Instead of useLocation
If you only need access to URL parameters or query parameters, avoid
using useLocation entirely. Instead, use React Router’s
more targeted hooks like useParams or
useSearchParams.
useParams: Re-renders only when the dynamic route parameters (e.g.,/user/:id) change.useSearchParams: Specifically handles query string parameters, allowing you to isolate query updates from general route updates.
import { useSearchParams } from 'react-router-dom';
export function SearchFilter() {
// More optimized than parsing useLocation().search manually
const [searchParams] = useSearchParams();
const query = searchParams.get('query');
return <div>Active Search: {query}</div>;
}Strategy 3: Implement a Custom Selector Hook
If you need to perform complex derivations from the location object,
you can create a custom hook that utilizes React’s useMemo
or a ref-based deep comparison to trigger state updates only when the
relevant values change.
import { useEffect, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
// Custom hook that only triggers a state update when the pathname changes
export function usePathname() {
const { pathname } = useLocation();
const [currentPath, setCurrentPath] = useState(pathname);
const prevPathRef = useRef(pathname);
useEffect(() => {
if (prevPathRef.current !== pathname) {
prevPathRef.current = pathname;
setCurrentPath(pathname);
}
}, [pathname]);
return currentPath;
}By isolating location listeners, utilizing React.memo
with primitive props, and switching to targeted hooks like
useParams, you can eliminate redundant renders and ensure
your React Router integration remains fast and efficient.