bug: TanStack Router adapter adds a second query string on routes with a dynamic segment
Context
What's your version of nuqs?
"nuqs": "2.10.1"What framework are you using?
- ❌ Next.js (app router)
- ❌ Next.js (pages router)
- ❌ React SPA (no router)
- ❌ Remix
- ❌ React Router
- ✅ Other (TanStack Router)
Which version of your framework are you using?
"@tanstack/react-router": "1.170.34"Description
With the TanStack Router adapter, a useQueryState write can add a second query string to the URL. The route's validateSearch then reads the second one, so the value I just set is thrown away and the control looks broken.
It happens when the route has a dynamic segment. updateUrl navigates with to: pathname + renderQueryString(search), but to is a path. buildLocation in router-core only splits a query string out of href, never out of to, so the whole string becomes the pathname and the router appends its own search on top.
I expected the write to produce one query string.
Reproduction
Route: /database/$table with a validateSearch that has defaults.
- Open
/database/customers?schema=public&limit=25&offset=0&view=data - Click a button that calls
setView("structure"), whereviewisuseQueryState("view", parseAsStringLiteral(["data", "structure"]).withDefault("data")) - The URL becomes:
/database/customers?schema=public&limit=25&offset=0&view=structure?schema=public&limit=50&offset=0&view=datalimit=50 is the route default, not the limit=25 that was on screen, so the tail comes from route resolution.
Note on reproducing it: on a route with no dynamic segment the glued path matches nothing, the appended search is empty, and the browser splits the pathname back correctly. So it only shows on routes like $table.
Still there with router-core 1.171.29.
Possible fix
Pass a search object instead of a query string:
navigate({
to: pathname,
search: objectFromSearchParams(next),
replace: options.history === "replace",
resetScroll: options.scroll,
})One thing to watch: objectFromSearchParams needs to turn repeated keys into an array, or routers with a custom parseSearch that supports ?filter=a&filter=b lose all but the last value.
I have this working locally with unstable_createAdapterProvider. Happy to send a PR if you want it.
Source: 47ng/nuqs