Skip to content

feat(pages): type useParams() from the page's URL pattern - #685

Draft
magurotuna wants to merge 3 commits into
voidzero-dev:mainfrom
magurotuna:feat/typed-use-params
Draft

magurotuna wants to merge 3 commits into
voidzero-dev:mainfrom
magurotuna:feat/typed-use-params

Conversation

@magurotuna

Copy link
Copy Markdown
Member

Pass a page's URL pattern to useParams() to type its params.

const { id } = useParams('/users/:id'); // id: string
useParams('/users/:id').idd;            // Type error
useParams('/userz/:id');                // Type error: no such page
  • Works in all four adapters. useParams() and useParams<T>() are unchanged, and no config flag is needed.
  • Only page patterns are accepted, not HTTP routes.
  • useParams(pattern) throws if the current page has no value for one of the pattern's params, instead of returning undefined typed as string.
  • void/routes exports PagePathMap, PagePath and PageParams<P>. Both the generated .void/routes.d.ts and the package declare them.

`useParams('/users/:id')` now returns `{ id: string }` in all four
adapters. The pattern must be one of the app's pages, so a misspelled
param or page is a type error.

- The generated `.void/routes.d.ts` adds `PagePathMap` (page patterns
  and their param names, without HTTP or WebSocket routes), plus
  `PagePath` and `PageParams<P>`. `void/routes` declares the same types
  for apps without generated declarations.
- An unnamed catch-all (`[...]`) has no param key, matching
  `c.req.param()`.
- At runtime, `useParams(pattern)` throws if the current page has no
  value for one of the pattern's params, so a component rendered in a
  different page doesn't read `undefined` typed as `string`. The check
  lives in `assertPageParams()` in `void/pages-client`.
- `useParams()` and `useParams<T>()` keep their current types.
…tern union

A pattern typed as '/users/:id' | '/files/:path{.+}' returned
{ id: string; path: string }, promising a param the current page may
not have. PageParams now distributes over the union.
@yangshun

yangshun commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Note that this PR adds runtime checks to useParams, so it can now bring down a page (might not be a bad thing, but it's new behavior) if the param shapes don't match.

I'm not sure if we want to do runtime checks within useParams. No other frameworks except TanStack Start do runtime checks within useParams.

If we're ok with just typechecking, consider this instead:

useParams<'/posts/:id'>();

or use your newly-added PageParams util:

useParams<PageParams<'/posts/:id'>>();

Since PageParams<'/posts/:id'> returns an object, useParams doesn't even require any changes.

@cpojer

cpojer commented Nov 3, 2026

Copy link
Copy Markdown
Member

I also prefer doing typechecking only and not failing during runtime for this.

@magurotuna

Copy link
Copy Markdown
Member Author

That makes sense, thanks for the insight! I'll make this static-check only

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants