Home/Learn/React/Route Params and Search Params as State

Route Params and Search Params as State

Intermediate
Routing

Path params identify a resource, search params describe a view. Treating search params as your filter state makes every view shareable and refresh-proof for free.

Overview

A URL is state that the browser persists, the user can copy, and the back button already knows how to undo. Most React apps under-use it — filters, sorting, tabs and pagination sit in useState, so a shared link shows something different from what the sender was looking at, and a refresh drops everything. The rule that resolves it is simple: anything that changes what is on screen and should survive a reload belongs in the URL. Only genuinely ephemeral state — a dropdown being open, a draft mid-typing — stays local.

Path Params

They identify the thing. Read them, validate them, and handle the missing case.

Params are strings, and they are user input
{ path: 'problems/:slug' }

const { slug } = useParams()      // always a string, never typed

// The URL is user input. Anyone can type /problems/../../etc
const { data, error } = useQuery({
  queryKey: ['problem', slug],
  queryFn: () => getProblem(slug),
})
if (error?.status === 404) return <NotFound />

// Numeric ids need parsing and checking
const id = Number(useParams().id)
if (!Number.isInteger(id)) return <NotFound />

// Optional and catch-all segments
{ path: 'problems/:slug?' }
{ path: 'docs/*' }                // useParams()['*'] holds the rest

// A changing param does NOT remount the component by default —
// the same route matched, so state persists across the navigation.
// Key on it when a fresh start is what you want:
<ProblemDetail key={slug} />

Search Params as Filter State

Read them like state, write them like navigation.

Preserve other params, reset the page, replace
const [searchParams, setSearchParams] = useSearchParams()

const topic = searchParams.get('topic') ?? 'all'
const page = Number(searchParams.get('page') ?? 1)
const sort = searchParams.get('sort') ?? 'newest'

function setFilter(key, value) {
  setSearchParams(prev => {
    const next = new URLSearchParams(prev)     // preserve the other params
    value ? next.set(key, value) : next.delete(key)
    next.delete('page')                        // reset paging on any filter
    return next
  }, { replace: true })                        // do not stack a history entry
}

// replace: true for filter tweaks — otherwise ten adjustments mean
// ten presses of back to leave the page. Use a real push for
// navigation the user would expect to undo.

// The query then follows the URL automatically, with no extra wiring
useQuery({ queryKey: ['problems', { topic, page, sort }], ... })

// Multi-select filters
searchParams.getAll('tag')        // ['arrays', 'graphs']

What Does Not Belong in the URL

The line, and the privacy rule that decides part of it.

Never put personal data in a query string
// In the URL — visible, shareable, refresh-proof
//   filters, sort, page, tab, search query, opened item id,
//   a modal that deserves its own address ("/problems/x?share=1")

// In component state — nobody wants to share these
//   a dropdown being open, hover state, an in-progress draft,
//   scroll position, whether a tooltip has been dismissed

// NEVER in the URL — it lands in server logs, browser history,
// referrer headers and analytics:
//   tokens, passwords, OTPs, email addresses, phone numbers,
//   anything personally identifying

// URLs get long. Encode properly and keep keys short:
next.set('q', 'two sum & more')      // URLSearchParams encodes for you

// A shared helper keeps parsing consistent across the app
function useFilters() {
  const [params, setParams] = useSearchParams()
  return {
    topic: params.get('topic') ?? 'all',
    page: Math.max(1, Number(params.get('page') ?? 1)),
    setFilter: (k, v) => { ... },
  }
}

Key Points to Remember

  • 1Path params identify a resource; search params describe how the view is filtered or sorted
  • 2Params arrive as strings and are user input — parse, validate and handle the invalid case
  • 3Preserve other search params when setting one, and reset the page whenever a filter changes
  • 4Use replace for filter adjustments so the back button leaves the page rather than undoing ten tweaks
  • 5Never put tokens or personal data in a query string — it is logged, cached and sent in referrers

Interview Questions

Sign in to ask Aria
1

Why put filter and pagination state in the URL instead of useState?

Medium
2

Why use replace rather than push when updating a filter?

Medium
3

What should never go into a query string, and why?

Medium

Ask Aria about Route Params and Search Params as State

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…