How many times have you installed a massive routing framework just to parse a query parameter or generate a breadcrumb trail?
In lightweight React applications, embedding heavy-weight navigation packages just to read from window.location
feels like overkill. Yet, writing your own state listeners to safely track dynamic changes—especially programmatic updates via history.pushState
—is surprisingly complex and error-prone.
That is why we just introduced the useURL
hook in the react-hook-lab library. It provides native, real-time, SSR-safe URL tracking and detailed analytical metadata with absolutely zero external dependencies.
React doesn't natively watch the URL. The standard window.location
object is not reactive; updating the search params or executing a history push does not trigger a re-render. Developers usually resort to:
popstate
(which misses programmatic routing like pushState
/replaceState
).useURL
solves this cleanly by globally monkey-patching pushState
and replaceState
once, exposing a fully reactive, deeply parsed URL representation that works across any standard React app.
Extracting search queries and acting on them is as simple as reading an object. useURL
automatically aggregates duplicate query parameters into clean arrays.
import React from 'react';
import { useURL } from 'react-hook-lab';
export function SearchInterface() {
const { query, pathname } = useURL();
// Safely parse single query parameters or lists
const searchTerm = typeof query.q === 'string' ? query.q : '';
const selectedFilters = Array.isArray(query.filter)
? query.filter
: query.filter
? [query.filter]
: [];
return (
<div style={{ padding: '16px', border: '1px solid #ccc' }}>
<h3>Active Route: {pathname}</h3>
<p>Search Term: <strong>{searchTerm || 'None'}</strong></p>
<div>
<span>Active Filters: </span>
{selectedFilters.map((filter) => (
<span key={filter} style={{ margin: '0 4px', background: '#ddd', padding: '2px 6px' }}>
{filter}
</span>
))}
</div>
</div>
);
}
useURL
maintains a reference to your previous path, tracking whether the route changed in the current session and compiling progressive breadcrumbs on the fly.
import React from 'react';
import { useURL } from 'react-hook-lab';
export function DynamicBreadcrumbs() {
const { breadcrumbs, previous, changed, timestamp } = useURL();
return (
<nav aria-label="breadcrumb" style={{ fontFamily: 'sans-serif' }}>
<ol style={{ display: 'flex', listStyle: 'none', padding: 0 }}>
{breadcrumbs.map((crumb, index) => (
<li key={crumb.path} style={{ marginRight: '8px' }}>
<a href={crumb.path}>{crumb.name}</a>
{index < breadcrumbs.length - 1 && <span style={{ marginLeft: '8px' }}>/</span>}
</li>
))}
</ol>
{changed && (
<p style={{ fontSize: '12px', color: '#666' }}>
You navigated here from <strong>{previous}</strong> at {new Date(timestamp).toLocaleTimeString()}.
</p>
)}
</nav>
);
}
Beyond basic pathnames and parameters, the return payload includes structural information:
filename
& extension
report
and pdf
from /assets/report.pdf
).parent
depth
isSecure
& isHome
Originally published on my blog. You can read the alternative breakdown here.