useLocation
Track and manage browser location, URL parameters, and navigation state in React applications.
useLocation
Track and manage browser location, URL parameters, and navigation state in React applications.
| Pathname | |
| Search | (empty) |
| Hash | (empty) |
| Full URL |
Installation
npx open-hook add useLocationyarn dlx open-hook add useLocationpnpm dlx open-hook add useLocationAPI Reference
Parameters
This hook takes no parameters.
Returns
| Prop | Type | Default |
|---|---|---|
setHash? | (hash: string) => void | - |
removeSearchParam? | (key: string) => void | - |
setSearchParam? | (key: string, value: string) => void | - |
reload? | (forceReload?: boolean) => void | - |
forward? | () => void | - |
back? | () => void | - |
navigate? | (url: string, replace?: boolean) => void | - |
hash? | string | - |
searchParams? | URLSearchParams | - |
location? | LocationState | - |
TypeScript Signature
interface LocationState {
pathname: string;
search: string;
hash: string;
host: string;
hostname: string;
origin: string;
port: string;
protocol: string;
href: string;
}
interface UseLocationReturn {
location: LocationState;
searchParams: URLSearchParams;
hash: string;
navigate: (url: string, replace?: boolean) => void;
back: () => void;
forward: () => void;
reload: (forceReload?: boolean) => void;
setSearchParam: (key: string, value: string) => void;
removeSearchParam: (key: string) => void;
setHash: (hash: string) => void;
}
function useLocation(): UseLocationReturn;Advanced Usage Examples
function ProductPage() {
const { searchParams, setSearchParam, removeSearchParam } = useLocation();
const category = searchParams.get("category") || "all";
const sortBy = searchParams.get("sortBy") || "name";
const page = parseInt(searchParams.get("page") || "1");
const updateFilter = (filter: string, value: string) => {
if (value === "all" || !value) {
removeSearchParam(filter);
} else {
setSearchParam(filter, value);
}
// Reset to page 1 when filtering
if (filter !== "page") {
setSearchParam("page", "1");
}
};
return (
<div>
<h1>Products</h1>
<select
value={category}
onChange={(e) => updateFilter("category", e.target.value)}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<select
value={sortBy}
onChange={(e) => updateFilter("sortBy", e.target.value)}
>
<option value="name">Sort by Name</option>
<option value="price">Sort by Price</option>
<option value="rating">Sort by Rating</option>
</select>
{/* Pagination */}
<div>
<button
onClick={() => updateFilter("page", (page - 1).toString())}
disabled={page <= 1}
>
Previous
</button>
<span>Page {page}</span>
<button onClick={() => updateFilter("page", (page + 1).toString())}>
Next
</button>
</div>
</div>
);
}function SearchPage() {
const { searchParams, setSearchParam, removeSearchParam, navigate } =
useLocation();
const query = searchParams.get("q") || "";
const type = searchParams.get("type") || "all";
const dateRange = searchParams.get("date") || "all";
const sortBy = searchParams.get("sort") || "relevance";
const [searchInput, setSearchInput] = useState(query);
const updateSearch = (newQuery: string) => {
if (newQuery.trim()) {
setSearchParam("q", newQuery.trim());
} else {
removeSearchParam("q");
}
};
const clearAllFilters = () => {
navigate(window.location.pathname); // Remove all query params
};
const buildShareableLink = () => {
const currentUrl = window.location.href;
navigator.clipboard.writeText(currentUrl);
};
const hasActiveFilters = Array.from(searchParams.entries()).length > 0;
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
updateSearch(searchInput);
}}
>
<input
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
placeholder="Search..."
/>
<button type="submit">Search</button>
</form>
<div className="filters">
<select
value={type}
onChange={(e) => setSearchParam("type", e.target.value)}
>
<option value="all">All Types</option>
<option value="posts">Posts</option>
<option value="users">Users</option>
<option value="comments">Comments</option>
</select>
<select
value={dateRange}
onChange={(e) => setSearchParam("date", e.target.value)}
>
<option value="all">All Time</option>
<option value="day">Last 24 hours</option>
<option value="week">Last week</option>
<option value="month">Last month</option>
</select>
<select
value={sortBy}
onChange={(e) => setSearchParam("sort", e.target.value)}
>
<option value="relevance">Relevance</option>
<option value="date">Date</option>
<option value="popularity">Popularity</option>
</select>
{hasActiveFilters && (
<button onClick={clearAllFilters}>Clear Filters</button>
)}
<button onClick={buildShareableLink}>Share Search</button>
</div>
<SearchResults
query={query}
type={type}
dateRange={dateRange}
sortBy={sortBy}
/>
</div>
);
}// Custom router hook using useLocation
function useRouter() {
const { location, navigate, back, forward, searchParams } = useLocation();
const push = useCallback(
(path: string) => {
navigate(path);
},
[navigate]
);
const replace = useCallback(
(path: string) => {
navigate(path, true);
},
[navigate]
);
const query = useMemo(() => {
const params: Record<string, string> = {};
searchParams.forEach((value, key) => {
params[key] = value;
});
return params;
}, [searchParams]);
return {
pathname: location.pathname,
search: location.search,
hash: location.hash,
query,
push,
replace,
back,
forward,
};
}
// Route component
function Route({
path,
component: Component,
}: {
path: string;
component: React.ComponentType<any>;
}) {
const { pathname } = useRouter();
// Simple path matching (you'd want more sophisticated matching in real apps)
const isMatch = pathname === path || pathname.startsWith(path + "/");
return isMatch ? <Component /> : null;
}
// App with routing
function App() {
const router = useRouter();
return (
<div>
<nav>
<button onClick={() => router.push("/")}>Home</button>
<button onClick={() => router.push("/about")}>About</button>
<button onClick={() => router.push("/contact")}>Contact</button>
</nav>
<main>
<Route path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
<Route path="/contact" component={ContactPage} />
</main>
<footer>
<button onClick={router.back}>← Back</button>
<button onClick={router.forward}>Forward →</button>
<span>Current: {router.pathname}</span>
</footer>
</div>
);
}Best Practices
Handle URL changes gracefully and maintain URL state consistency:
const { searchParams, setSearchParam, location } = useLocation();
// Always validate URL parameters
const page = Math.max(1, parseInt(searchParams.get("page") || "1"));
const validSortOptions = ["name", "date", "price"];
const sortBy = validSortOptions.includes(searchParams.get("sort"))
? searchParams.get("sort")
: "name";
// Batch URL updates to avoid multiple history entries
const updateFilters = (newFilters: Record<string, string>) => {
const newSearchParams = new URLSearchParams(location.search);
Object.entries(newFilters).forEach(([key, value]) => {
if (value) {
newSearchParams.set(key, value);
} else {
newSearchParams.delete(key);
}
});
const newUrl = `${location.pathname}?${newSearchParams.toString()}`;
navigate(newUrl, true); // Replace current entry
};URL Management Guidelines
- 🔗 Shareable URLs: Keep important state in URL parameters
- 📱 Deep Linking: Support direct navigation to specific app states
- 🔄 History Management: Use replace for filter updates, push for navigation
- ✅ Validation: Always validate URL parameters before using them
Do's and Don'ts
- ✅ Use URL parameters for shareable application state
- ✅ Validate URL parameters and provide fallbacks
- ✅ Batch URL updates to avoid excessive history entries
- ✅ Provide clear navigation feedback
- ❌ Don't store sensitive data in URL parameters
- ❌ Avoid creating too many history entries with rapid updates
- ❌ Don't rely on URL state for temporary UI state
- ❌ Avoid breaking the back button behavior
Performance Metrics
| Metric | Value | Description |
|---|---|---|
| Bundle Size | ~1.2kb | Minified + gzipped |
| Update Speed | < 1ms | URL state synchronization |
| Memory Usage | Low | Minimal state tracking |
| Browser Events | 3 listeners | popstate, hashchange, history |
Browser Compatibility
| Browser | Supported | Notes |
|---|---|---|
| Chrome 4+ | ✅Yes | Full history API support |
| Firefox 4+ | ✅Yes | Full history API support |
| Safari 5+ | ✅Yes | Full history API support |
| Edge 12+ | ✅Yes | Full history API support |
| IE 10+ | ✅Yes | Limited history API support |
| Mobile browsers | ✅Yes | Modern mobile browsers |
Use Cases
URL State Management
Keep application state synchronized with URL for shareability and bookmarking.
Custom Routing
Build lightweight routing solutions without external router libraries.
Deep Linking
Enable direct navigation to specific application states via URLs.
Navigation Control
Programmatically control browser navigation and history.
Accessibility
Accessibility Considerations
- Announce route changes to screen readers - Ensure focus management during navigation - Provide skip links for complex navigation - Use semantic HTML for navigation elements - Maintain logical tab order during route changes
Troubleshooting
Internals
How It Works
The hook maintains synchronized state with the browser's location object by listening to popstate, hashchange, and custom history events. It provides a reactive interface to the History API while maintaining compatibility with browser navigation controls.
The implementation intercepts pushState and replaceState calls to ensure all location changes trigger React re-renders.
Testing Example
import { renderHook, act } from "@testing-library/react";
import { useLocation } from "./useLocation";
// Mock window.location and history
const mockLocation = {
pathname: "/test",
search: "?foo=bar",
hash: "#section",
host: "example.com",
hostname: "example.com",
origin: "https://example.com",
port: "",
protocol: "https:",
href: "https://example.com/test?foo=bar#section",
};
Object.defineProperty(window, "location", {
value: mockLocation,
writable: true,
});
const mockHistory = {
pushState: jest.fn(),
replaceState: jest.fn(),
back: jest.fn(),
forward: jest.fn(),
};
Object.defineProperty(window, "history", {
value: mockHistory,
writable: true,
});
describe("useLocation", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should return current location state", () => {
const { result } = renderHook(() => useLocation());
expect(result.current.location.pathname).toBe("/test");
expect(result.current.location.search).toBe("?foo=bar");
expect(result.current.location.hash).toBe("#section");
expect(result.current.searchParams.get("foo")).toBe("bar");
});
it("should navigate to new URL", () => {
const { result } = renderHook(() => useLocation());
act(() => {
result.current.navigate("/new-path");
});
expect(mockHistory.pushState).toHaveBeenCalledWith(null, "", "/new-path");
});
it("should replace current URL", () => {
const { result } = renderHook(() => useLocation());
act(() => {
result.current.navigate("/new-path", true);
});
expect(mockHistory.replaceState).toHaveBeenCalledWith(
null,
"",
"/new-path"
);
});
it("should set search parameters", () => {
const { result } = renderHook(() => useLocation());
act(() => {
result.current.setSearchParam("test", "value");
});
expect(mockHistory.replaceState).toHaveBeenCalledWith(
null,
"",
expect.stringContaining("test=value")
);
});
it("should remove search parameters", () => {
const { result } = renderHook(() => useLocation());
act(() => {
result.current.removeSearchParam("foo");
});
expect(mockHistory.replaceState).toHaveBeenCalledWith(
null,
"",
expect.not.stringContaining("foo=bar")
);
});
it("should handle browser navigation", () => {
const { result } = renderHook(() => useLocation());
act(() => {
result.current.back();
});
expect(mockHistory.back).toHaveBeenCalled();
act(() => {
result.current.forward();
});
expect(mockHistory.forward).toHaveBeenCalled();
});
});FAQ
Related Hooks
- useClipboard - Copy current URL for sharing
- usePrevious - Track previous location state
- useLocalStorage - Persist navigation preferences
Changelog
- 2.0.0 — Enhanced TypeScript support, improved search parameter handling, better browser compatibility, comprehensive navigation methods
- 1.3.0 — Added hash management and search parameter utilities
- 1.2.0 — Improved history API integration and event handling
- 1.1.0 — Added navigation methods (back, forward, reload)
- 1.0.0 — Initial release with basic location tracking