47ng / 47ng/nuqs

bug: TanStack Router adapter adds a second query string on routes with a dynamic segment

Offen
#1,590 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
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

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.