[RFC]: Type-Safe URL Search Params for useSearchParams
hook
#13713
Replies: 5 comments 19 replies
-
A few questions:
|
Beta Was this translation helpful? Give feedback.
-
Without getting too deep in the weeds here, this proposal should be targeting the routes.ts file rather than another export bloating the surface API as @alexanderson1993 mentioned. This needs very careful consideration of locking in a validation library (or requiring one at all?) and should be typegen first.
|
Beta Was this translation helpful? Give feedback.
-
I have some thoughts about implementing type-safety searchParams into react-router though I chose a slightly different approach.
export function validateSearchParams<T>(search: URLSearchParams): T; It won't require any library ( type User = { name: string; email: string }
export function validateSearchParams(search: URLSearchParams) {
const name = search.get('user') || ''
const email = search.get('email') || ''
return { name, email }
} Same for import * as v from 'valibot'
const userSchema = v.object({ user: v.string(), email: v.string() })
export function validateSearchParams(search: URLSearchParams) {
return v.parse(userSchema, Object.fromEntries(search))
}
The flow of execution should look like that: Server: Thanks to that we can set new property in export function validateSearchParams(url: URLSearchParams) {
return { user: ..., email: ... }
}
export async function loader({ search }: Route.LoaderArgs) {
// do something with search
}
export default function Route({ search }: Route.ComponentProps) {
const { user, email } = search
return <p>{user}</p>
}
In that case import * as IndexRoute from './index.tsx'
// or import { validateSearchParams as indexSearchParams } from './index.tsx'
export function validateSearchParams(search: URLSearchParams) {
return {
...Route.validateSearchParams(search),
filter: { sort: 'desc' }
}
}
import { Link } from 'react-router'
<Link to={{ pathname: href('/users/:id', { id: '1' }), search: search('/users/:id', { filter: {} }) /> Unfortunately in that case we duplicate param export function href<Path extends keyof Args>(
path: Path,
...args: Args[Path]
): string If we could safely change it into this: export function href<Path extension keyof Args>(path: Path, args: Args[Path], search: SearchArgs[Path]) Then we would have fully typed import { Link } from 'react-router'
<Link to={href('/users/:id', { id: '1' }, { filter: {} })} />
// or
<Link to={href('/users/:id', { id: '1' }, prev => ({ ...prev, filter: {} }) } />
|
Beta Was this translation helpful? Give feedback.
-
In my opinion type safe search params should be implemented in the routes definition: export default [
index("./home.tsx"),
route("/about", "./about.tsx"),
route("/city", "./city.tsx", { searchParam1: z.string() }),
] satisfies RouteConfig; This way you also have type safe href('/city', { searchParam1: 'x' }) You get type safe search params in components routes: export default function Page({ params: { searchParam1 }: Route.ComponentProps }) {
// ...
} |
Beta Was this translation helpful? Give feedback.
-
I opened a separate RFC for adding the search params in the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
RFC: Type-Safe URL Search Parameters for
useSearchParams
Last Edited: June 9, 2025
This proposal is made according to the guidelines provided here: GOVERNANCE.md - New Feature Process.
Edit Summary
This RFC is pending edits to address newly highlighted concerns and suggestions. Please refer to this thread for a more likely path forward for the proposal.
Problem
useSearchParams
currently exposes the untypedURLSearchParams
API. This results in runtime errors, boilerplate, and poor developer experience due to a lack of compile-time safety, inconsistent with React Router's strong type support for path parameters and loader/action data.Motivation
To elevate
useSearchParams
to the same standard of type safety as other core React Router APIs. By integrating a standard schema-based approach for search params, we can:useSearchParams
with the library's existing type system.typegen
), seamless type consistency forloaders
andactions
, and overall API standardization, cannot be effectively achieved or maintained in user-land without significant boilerplate or breaking the "Routing and Data Focused" design principle.Proposed Solution: New
searchParamsSchema
Route Module Export & EnhanceduseSearchParams
HookWe propose introducing a new, optional
searchParamsSchema
export within route modules. This export would be an object returned by a React Router utility (e.g.,defineSearchParams
) that encapsulates individual schema definitions for each search parameter.react-router typegen
would process this to type the route'sInfo
object. Concurrently, theuseSearchParams
hook will be enhanced to accept an optional schema, providing typed state when a schema is provided, while maintaining backward compatibility by returning an untypedURLSearchParams
object when no schema is passed.New Utilities and Concepts
defineSearchParams
utility: A new function used to create search parameter schemas. It takes a plain object where each property's value is an individual schema definition (e.g.,z.string()
,z.number()
). These schemas can be used in route modules or directly with theuseSearchParams
hook.searchParamsSchema
export: A new, optional export from route modules. It holds the schema definition for that route's search parameters, created usingdefineSearchParams
. This export serves as the canonical source for internal router processes (likeloaders
/actions
) to parse and typeURLSearchParams
for their specific context.useSearchParams
hook: The existing hook is updated. It now optionally accepts asearchParamsSchema
object to provide type-safe access and updates to URL search parameters. If no schema is provided, it behaves as it currently does.Info.searchParams
type: A new property generated byreact-router typegen
within a route'sInfo
type, providing compile-time type safety for search parameters based on thesearchParamsSchema
export.null
. No errors are surfaced.Core Concepts
Dedicated
searchParamsSchema
Route Module Export:A new, optional
searchParamsSchema
export will be an object created by a React Router utility function, likedefineSearchParams({ param1: z.string(), param2: z.number() })
. This declares the canonical types and validation rules for URL search parameters pertinent to this specific route. This export is crucial for internal router processes (such asloaders
andactions
) to consistently parse and typeURLSearchParams
for their associated route's context.Internal Processing & Validation:
When a schema is provided to
useSearchParams
(or used by other router internals), React Router's runtime internally parses and validatesrequest.url.searchParams
against the provided schema.null
.setParams
, any existing same-named parameters are overwritten by data conforming to the writer's schema.Type Generation Integration:
react-router typegen
identifies and processes thesearchParamsSchema
export. It generates a correspondingsearchParams
property within the route'sInfo
type (e.g., in.react-router/types/app/routes/+types/products.ts
). This type will be based directly on the schema's inferred type (including potentialnull
orundefined
types based on schema defaults).Example generated type snippet within
Info
:Enhanced
useSearchParams
Hook:The
useSearchParams
hook is enhanced to:useSearchParams()
), it returns the current untypedURLSearchParams
object and updater.useSearchParams({ schema: mySchema })
), it returns a tuple where the first element (params
) is a strongly typed object based on the provided schema, and the second (setParams
) accepts an object conforming to that schema for type-safe updates.searchParamsSchema
exported by the current route module, or any other schema defined elsewhere in the application.Example Implementation
app/routes/products.tsx
:Advantages
useSearchParams()
calls continue to work unchanged.searchParamsSchema
export provides a canonical, typegen-discoverable schema for routes, crucial for consistent internal parsing inloaders
/actions
.Disadvantages / Trade-offs
useSearchParams
does not automatically infer thesearchParamsSchema
from the module context; it must be explicitly provided. This trade-off provides the flexibility for the hook to be used with any schema, not solely restricted to the route's module definition.Community Alternatives
The React ecosystem has developed solutions to address the lack of type-safe URL search parameters, which this proposal builds upon and aims to integrate natively.
nuqs: A dedicated, type-safe search params state manager for React. This proposal is heavily inspired by
nuqs
's declarative, parser-driven approach to managing URL state.Custom Hooks for Abstraction & Validation: A common pattern as described in an X post by Cory House involves creating custom React hooks that encapsulate
useSearchParams
, abstracting away the manual parsing, validation (e.g., with Zod), and serialization logic. An example of this approach is shown in the following snippet:This proposal aims to bring the core benefits of these patterns directly into
useSearchParams
for a more integrated and consistent first-party experience.Beta Was this translation helpful? Give feedback.
All reactions