Siddhant DevalAuthor
Senior Full-Stack Engineer·Aug 27, 2026·9 min read
The Underrated State Stores: URL Params & Form State
The URL and the DOM input already hold state. Duplicating them into useState wastes renders, breaks the browser back button, and destroys shareability. This article covers URL-as-state with useSearchParams, the security boundaries of URL params, and why React Hook Form's uncontrolled-by-default approach eliminates render cascades in large forms.
Technical Series
Frontend State Architecture
Part 5 of 8
The Underrated State Stores: URL Params & Form State
The first pillar — compute, don't store — applies with maximum force to two domains that most developers reflexively reach for
useState to handle: UI filters/search/pagination driven by the URL, and form input values.The URL already holds state. The DOM input already holds state. Duplicating them into React state is wasteful, breaks the browser back button, destroys shareability, and introduces the two-sources-of-truth problem. This article covers how to use these free state stores correctly — and where their boundaries are.
1. The URL as the Ultimate Global State
Query parameters have properties that no library-based global state store can match:
| Property | URL Params | Redux / Zustand |
|---|---|---|
| Bookmarkable | ✅ Yes | ❌ No |
| Shareable | ✅ Yes | ❌ No |
| Browser back/forward | ✅ Native | ❌ Manual |
| Server-renderable | ✅ Yes | ❌ No |
| Survives page refresh | ✅ Yes | ❌ No |
| Zero library cost | ✅ Yes | ❌ ~1–11 KB |

Expand
Filters, sort order, search queries, active tab, pagination offset — these are all UI state that belongs in the URL. A user should be able to share the URL
?category=frontend&sort=popular&page=2 with a colleague and have them land on the exact same view.2. useSearchParams in Next.js
The
useSearchParams hook reads the current URL's query string as a URLSearchParams instance:tsx
Pro Tip & Optimization
Never mirror URL params into local
useState. The pattern const [category, setCategory] = useState(searchParams.get('category')) creates two sources of truth that immediately diverge when the user hits the back button. Read directly from searchParams on every render — it is reactive and always reflects the current URL.The consuming component derives its filtered list directly from the URL values:
tsx
3. URL Security Boundaries
URL state comes with a critical constraint that must be understood before using it:
Performance / Safety Warning
URL query parameters are not private. They are:
- Logged by web servers in access logs (e.g., NGINX, Apache).
- Logged by CDN/proxy providers (Cloudflare, AWS CloudFront) in their request logs.
- Stored in browser history, accessible to other users of the same device.
- Sent in the
Refererheader when a user clicks a link from your page to an external site.
Never put in query parameters:
- Access tokens, session IDs, API keys
- Passwords or password reset tokens (use POST body + short-lived tokens)
- Personally Identifiable Information (PII) like SSNs, credit card numbers
- Anything that grants permissions or proves identity
Safe for query parameters:
- Filters (
?category=frontend) - Sort order (
?sort=popular) - Pagination (
?page=3) - Search queries (
?q=react+hooks) - Non-sensitive resource IDs (
?postId=abc123)
4. Controlled vs. Uncontrolled Inputs
React inputs exist on a spectrum:
tsx
Controlled inputs give React full ownership: every keystroke fires
onChange, which calls setState, which re-renders. This is the right choice when:- You need to validate or transform input in real time.
- The input value needs to drive other UI changes immediately (e.g., live search).
- You need to programmatically clear or reset the field.
Uncontrolled inputs let the DOM hold the value: React only reads it on demand (submit, blur). This is the right choice when:
- You have a large form with many fields.
- Real-time validation is not needed.
- Performance is a concern.
5. The Controlled Form Render Cascade
Here is the hidden cost of all-controlled forms at scale:
tsx
With 20 fields, a user typing a name triggers 20-component re-renders per character. On fast hardware this is imperceptible. On a 4× CPU throttle (mobile device) or in a complex form with heavy validation logic, it becomes measurable jank.

Expand
6. React Hook Form: Uncontrolled by Default
React Hook Form (RHF) solves this by using DOM refs to read field values rather than React state. Fields are registered once; the library reads
.value from the DOM ref on submit or on validation trigger — no onChange cascade.tsx
The entire form above causes zero re-renders during typing. RHF only triggers re-renders for
formState changes (error states, isSubmitting, isDirty), not for field value changes.Crucial Requirement
watch('fieldName') in React Hook Form is a reactive subscription — it does cause re-renders on every change to that field. Use it only when you genuinely need to react to a field value in real-time (e.g., showing a character count, conditionally rendering other fields). Avoid watch in form-level components; prefer getValues() inside event handlers instead.7. The State Management Decision for Forms
| Need | Tool |
|---|---|
| Simple form, no real-time validation | Uncontrolled inputs + ref |
| Large form, submit-time validation | React Hook Form + Zod |
| Multi-step wizard with shared state | React Hook Form useFormContext |
| Real-time live search input | Controlled useState with debounce |
| Filter/sort/pagination UI | URL params (useSearchParams) |
| Complex cross-field validation | Zod superRefine or refine |
8. References
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#React#URL State#useSearchParams#React Hook Form#Form State#Zod#State Management