Building a Multi-Page SPA with React RouterEntry 10 of 20
Query Params with useSearchParams
useSearchParams() reads and writes the URL's query string (?role=admin), and re-renders the component when it changes.
Notes
- Returns
[searchParams, setSearchParams], likeuseStatefor the URL. searchParamsis a standardURLSearchParams:.get('role'),.getAll('tag'),.has('page').setSearchParams({ role: 'admin' })updates the URL with client-side navigation (no reload).- In loaders, read query params from the request:
new URL(request.url).searchParams. - Query params suit shareable UI state: filters, sorting, pagination, search terms.
What / Why / When
- What
URL-backed state.
- Why
Filters survive refresh and can be shared or bookmarked.
- When
Any state the user expects to keep when copying the URL.
Code compare
import { useSearchParams } from "react-router";const ROLES = ["admin", "manager", "user"] as const;export default function UsersPage() { const [searchParams, setSearchParams] = useSearchParams(); const role = searchParams.get("role") ?? "all"; return ( <> <p>Filter: {role}</p> {ROLES.map((r) => ( <button key={r} onClick={() => setSearchParams({ role: r })}> {r} </button> ))} </> );}// Clicking "admin" → /users?role=admin, component re-renders// Reading query params in a loaderexport async function loader({ request }: LoaderFunctionArgs) { const role = new URL(request.url).searchParams.get("role"); return getUsers(role);}With withComponentInputBinding(), query params also bind to inputs.
@Component({ selector: "app-users", template: ` <p>Filter: {{ role() ?? "all" }}</p> @for (r of roles; track r) { <button (click)="setRole(r)">{{ r }}</button> } `,})export class UsersComponent { private router = inject(Router); private route = inject(ActivatedRoute); role = input<string>(); // bound from ?role= protected roles = ["admin", "manager", "user"]; setRole(role: string) { this.router.navigate([], { relativeTo: this.route, queryParams: { role }, queryParamsHandling: "merge", }); }}Differences and gotchas
setSearchParams({ role })replaces all query params. Use the function form to keep others:setSearchParams((prev) => { prev.set('role', r); return prev; }).- Angular's
queryParamsHandling: 'merge'keeps existing params.
Quick reference
| Task | React | Angular |
|---|---|---|
| Read | searchParams.get('role') | role = input<string>() / ActivatedRoute.queryParamMap |
| Write | setSearchParams({ role }) | router.navigate([], { queryParams: { role } }) |
| Keep others | Function form of setSearchParams | queryParamsHandling: 'merge' |
| In a loader/resolver | new URL(request.url).searchParams | route.queryParamMap |