ref: unify optimistic state into a single overlay store - #1482
Conversation
The typed-value emitter and the queued-query store were two channels describing the same pending URL state. One per-key channel on the throttle queue removes the duplication, makes sibling hooks re-parse raw queries with their own parser (the emitter pushed the writer's parsed value, wrong type on cross-parser binds), and gives debounce(Infinity) the same all-hooks visibility as throttle(Infinity).
Duplicated dist copies (bundler or version duplication) each ran their own throttle accounting against the browser's single History API budget, risking Safari's SecurityError. Anchoring lastFlushedAt on history.nuqs converges them, and a module-load counter surfaces the duplication (NUQS-409).
The defaults memo coerced object defaults to '[object Object]', freezing them at first render. Comparing with the parser's eq keeps identities stable across renders while letting genuinely-changed defaults through. The setter reads parsers and defaults through refs so reactive default changes never churn its identity nor leave clearOnDefault comparing against a stale default.
Hooks binding the same key with the same parser re-parsed identical raw queries into distinct objects, breaking cross-consumer referential stability (and causing downstream memo/effect churn). A cache keyed on url key, parse function identity and raw query yields one shared value; double-binds miss gracefully, and the server bypasses it to keep requests isolated.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
The overlay is cleared before updateUrl runs; when the browser rejects the update (Safari rate limit), hooks would fork silently: the writer kept its optimistic value while siblings reverted one by one on unrelated re-renders. Notifying the failed keys makes every hook agree with the URL again, matching the self-healing the debounce path already had.
Released versions assume `adapters` exists whenever the slot does and crash on push otherwise; seeding it preserves them under version skew, the exact duplication scenario the queue detector exists to catch. The 409 message now also covers same-version copies and non-adapter duplication sources.
Caching a failure as null made the warning a one-shot: a stuck widget could never be correlated to its malformed URL param after the first parse. Re-warning on failed-entry hits keeps the signal alive (debug mode only). Refreshing an existing key at capacity no longer evicts an unrelated entry, and the server-bypass branch is now pinned by a node test.
The rawValues comment attributed optimistic-update filtering to the identity gate when it belongs to the queryRef cache; trusting it could get the load-bearing setter-side queryRef write removed. Also aligns stale wording (queued-queries naming, writer identity caveat, debug message) and pins the eq-less object defaults identity churn as the accepted #1193 trade-off.
Implements steps 1 to 4 of the single-store strategy. The cross-hook typed-value emitter and the queued-query store were two channels describing the same pending URL state, which duplicated machinery, hid a cross-parser adoption bug, and stood in the way of key isolation work.
lib/sync.ts. Hooks read a merged raw value (pending overlay, falling back to committed params) throughuseSyncExternalStore, and sibling hooks re-parse raw queries with their own parser instead of adopting the writer's parsed value.history.nuqs, so duplicated dist copies share the one Safari History API budget, with a NUQS-409-style duplicate-queue detector.eq, making object and Date dynamic defaults reactive while keeping stable identities and a stable setter. Semver note: apps relying on the frozen-defaults bug will now see defaults update.debounce(Infinity)now behaves likethrottle(Infinity): the value lands in the overlay, visible to all hooks on the key, and the flush is deferred. The previous sibling-sync-only behavior was an artifact of the deleted emitter.The e2e suites were treated as the spec and are untouched: all 2064 tests pass across the 8 cells, plus 419 unit, browser and type tests. Bundle: client 5.70 of 6 kB, minimal tree-shaken 4.19 of 4.5 kB.
Closes #1193