Open gijsroge opened 3 weeks ago
Thanks, sorry it took so long to get into this issue.
It looks like the Remix adapter needs to follow the same optimistic search params update we use in Next.js (and really all the other adapters should follow the same pattern).
Let's assume we're typing "ab" in quick succession. The root of the issue was that, when the first request (for the first character typed, "a") is in flight, Remix's useSearchParams
still returns the stale empty value, while the internal state is optimistically updated. Before it has a chance to resolve, we queue in the full "ab" value.
When the "?search=a" loader returns, the URL updates along with useSearchParams to search: "a"
. This causes the internal state to sync back to that temporary value, causing the flicker in the input. When the URL update finishes after queuing and the loader delay, useSearchParams resolves to search: "ab"
and the UI is now eventually consistent.
The fix for this in Next was to use the useOptimistic
experimental React hook. It was possible because Next app router embeds a canary version of React where useOptimistic
is available, something we can't enforce for Remix users who likely use React 18.
I did some tests by following the same pattern and hacking together a useOptimisticSearchParams
hook for Remix, which also adds shallow: true | false
support (see my post on Bluesky).
Currently, the shallow option doesn't have an effect in Remix, everything was as if shallow was set to false, in contradiction with the docs. According to the Remix team, that is intentional: when the URL changes, loaders should re-run. In our case it's an issue as nuqs assumes local client-only updates by default, and only opts-in to server updates via shallow: false
if there is an actual use-case for SSR needing the search params in loaders or Server Components in Next.
Now as to why useQueryStates works: I think that's a double bug.. 😅 It looks like the sync mechanism fails at the right moment, skipping that temporary ?search=a
resolution. Another thing to look into.
Context
What's your version of
nuqs
?What framework are you using?
Which version of your framework are you using?
Description
Whenever the server finishes a request the component get's re-rendered and the useQueryState returns an old search state.
useQueryStates
does not have this issue, seems to only be happening withuseQueryState
If I read the docs I would assume this should not be the case https://nuqs.47ng.com/docs/options#throttling-url-updates
the state returned by the hook is always updated instantly, to keep UI responsive. Only changes to the URL, and server requests when using shallow: false, are throttled.
Reproduction
https://stackblitz.com/~/github.com/gijsroge/nuqs-throttle-issue?file=app/routes/_index.tsx
If you type in the search box you will notice the issue.
This issue is only apparent if you use the throttle feature.