patterntypescriptreactTip
URL as state — useSearchParams for shareable filter state
Viewed 0 times
useSearchParamsURL statefilter stateshareable URLsearch paramsquery stringpagination URL
Problem
Filter, sort, and pagination state stored in useState is lost on page refresh and cannot be shared via URL. Users cannot bookmark filtered views or share links that reproduce their exact search state.
Solution
Sync filter state to URL search params; read from URL on mount:
// React Router v6
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') ?? 'all';
const page = Number(searchParams.get('page') ?? '1');
const sort = searchParams.get('sort') ?? 'name';
const setFilter = (key: string, value: string) => {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set(key, value);
if (key !== 'page') next.set('page', '1'); // reset page on filter change
return next;
});
};
return (
<div>
<select value={category} onChange={(e) => setFilter('category', e.target.value)}>
<option value="all">All</option>
<option value="books">Books</option>
</select>
<Products category={category} page={page} sort={sort} />
</div>
);
}
// Next.js App Router equivalent:
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
function useFilter() {
const router = useRouter();
const pathname = usePathname();
const params = useSearchParams();
const setFilter = (key: string, value: string) => {
const next = new URLSearchParams(params.toString());
next.set(key, value);
router.push(
};
return { params, setFilter };
}
// React Router v6
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') ?? 'all';
const page = Number(searchParams.get('page') ?? '1');
const sort = searchParams.get('sort') ?? 'name';
const setFilter = (key: string, value: string) => {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set(key, value);
if (key !== 'page') next.set('page', '1'); // reset page on filter change
return next;
});
};
return (
<div>
<select value={category} onChange={(e) => setFilter('category', e.target.value)}>
<option value="all">All</option>
<option value="books">Books</option>
</select>
<Products category={category} page={page} sort={sort} />
</div>
);
}
// Next.js App Router equivalent:
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
function useFilter() {
const router = useRouter();
const pathname = usePathname();
const params = useSearchParams();
const setFilter = (key: string, value: string) => {
const next = new URLSearchParams(params.toString());
next.set(key, value);
router.push(
${pathname}?${next.toString()});};
return { params, setFilter };
}
Why
URL search params are the natural persistence layer for user-controlled view state. They are automatically serialised/deserialised as strings, integrate with browser history, and make views shareable and bookmarkable.
Gotchas
- All search param values are strings — always parse numbers and booleans explicitly
- setSearchParams replaces the entire search string by default — use the callback form with URLSearchParams to merge changes
- In Next.js App Router, useSearchParams() requires a Suspense boundary when used in a Client Component
- Avoid syncing ephemeral state (hover, focus) to the URL — only persist state that meaningfully changes the view
Revisions (0)
No revisions yet.