bug: TanStack Router adapter adds a second query string on routes with a dynamic segment
- Vorherrschende Sprache
- TypeScript
- Sterne
- 10.8k
- Forks
- 294
- Ø Merge
- 1 T. 16 Std.
- Gemergte PRs (30 T.)
- 21
Beschreibung
## 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.
1. Open `/database/customers?schema=public&limit=25&offset=0&view=data`
2. Click a button that calls `setView("structure")`, where `view` is `useQueryState("view", parseAsStringLiteral(["data", "structure"]).withDefault("data"))`
3. The URL becomes:
```
/database/customers?schema=public&limit=25&offset=0&view=structure?schema=public&limit=50&offset=0&view=data
```
`limit=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:
```ts
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.
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.