Contents

Frontend Development › State Management & Data Fetching

URL as State

Keeping filters, tabs and pagination in the URL so it can be shared.

Also known as: state in the URL, query string state, URL state, shareable state, deep linking state

Some UI state belongs in the URL: the current filters, sort order, search text, selected tab and page number. Putting it there makes the page shareable, bookmarkable and refresh-safe, and makes the browser’s back button behave the way people expect.

/products?category=kettles&sort=price&page=2&q=steel

Copy that link and send it: your colleague sees exactly the same list. Refresh the page, and nothing resets. Press Back, and you return to the previous filter instead of leaving the site.

Compare with keeping the same state in useState: reload, and the filters vanish. Share the link, and the recipient sees defaults.

What to put in the URL

In the URLNot in the URL
Search text, filters, sortingOpen/closed dropdowns and tooltips
Pagination or cursorHover and focus state
Selected tab or view modeTransient form input before submission
Which item is selected or openSensitive data and secrets
Date ranges, map position/zoomLarge or fast-changing data

Rule of thumb: if someone would want to link to this exact view, it belongs in the URL.

Implementing it

// React Router style
const [params, setParams] = useSearchParams();
const category = params.get("category") ?? "all";
const page = Number(params.get("page") ?? 1);

function setCategory(next: string) {
  setParams((p) => { p.set("category", next); p.set("page", "1"); return p; });   // reset the page
}

The URL becomes the single source of truth: components read from it, and update it, and the data fetch is keyed by the params (data fetching libraries).

Details that matter

  • Parse and validate. URLs are user-editable input. Handle missing, malformed or out-of-range values by falling back to defaults (runtime validation).
  • Omit defaults to keep URLs short and clean.
  • Reset dependent state (page number) when a filter changes.
  • Debounce typed text before updating the URL (debounce).
  • Choose push vs replace: pushState adds a history entry (good for navigation-like changes). replaceState doesn’t (good for every keystroke in a search box). See the History API.
  • Encode values properly (URL encoding).
  • Don’t put secrets or personal data in URLs, since they end up in logs and history.

A decent default when choosing where state lives: URL first for view state that matters, then local component state, then context or a store.