diff --git a/packages/nuqs/src/adapters/defs.ts b/packages/nuqs/src/adapters/defs.ts index 4986ead9..a1a0bfaf 100644 --- a/packages/nuqs/src/adapters/defs.ts +++ b/packages/nuqs/src/adapters/defs.ts @@ -12,5 +12,6 @@ export type UseAdapterHook = () => AdapterInterface export type AdapterInterface = { searchParams: URLSearchParams updateUrl: UpdateUrlFunction + getSearchParamsSnapshot?: () => URLSearchParams rateLimitFactor?: number } diff --git a/packages/nuqs/src/adapters/testing.ts b/packages/nuqs/src/adapters/testing.ts index 21b40a6a..ed8d9f21 100644 --- a/packages/nuqs/src/adapters/testing.ts +++ b/packages/nuqs/src/adapters/testing.ts @@ -36,6 +36,9 @@ export function NuqsTestingAdapter({ options }) }, + getSearchParamsSnapshot() { + return new URLSearchParams(props.searchParams) + }, rateLimitFactor: props.rateLimitFactor ?? 0 }) return createElement( diff --git a/packages/nuqs/src/update-queue.ts b/packages/nuqs/src/update-queue.ts index 12c6890d..83684a6a 100644 --- a/packages/nuqs/src/update-queue.ts +++ b/packages/nuqs/src/update-queue.ts @@ -1,4 +1,4 @@ -import type { UpdateUrlFunction } from './adapters/defs' +import type { AdapterInterface } from './adapters/defs' import { debug } from './debug' import type { Options } from './defs' import { error } from './errors' @@ -76,15 +76,19 @@ export function enqueueQueryStringUpdate( * * @returns a Promise to the URLSearchParams that have been applied. */ -export function scheduleFlushToURL( - updateUrl: UpdateUrlFunction, - rateLimitFactor: number -) { +export function scheduleFlushToURL({ + getSearchParamsSnapshot = () => new URLSearchParams(location.search), + updateUrl, + rateLimitFactor = 1 +}: Pick< + AdapterInterface, + 'updateUrl' | 'getSearchParamsSnapshot' | 'rateLimitFactor' +>) { if (flushPromiseCache === null) { flushPromiseCache = new Promise((resolve, reject) => { if (!Number.isFinite(queueOptions.throttleMs)) { debug('[nuqs queue] Skipping flush due to throttleMs=Infinity') - resolve(new URLSearchParams(location.search)) + resolve(getSearchParamsSnapshot()) // Let the promise be returned before clearing the cached value setTimeout(() => { flushPromiseCache = null @@ -93,7 +97,10 @@ export function scheduleFlushToURL( } function flushNow() { lastFlushTimestamp = performance.now() - const [search, error] = flushUpdateQueue(updateUrl) + const [search, error] = flushUpdateQueue({ + updateUrl, + getSearchParamsSnapshot + }) if (error === null) { resolve(search) } else { @@ -129,10 +136,14 @@ export function scheduleFlushToURL( return flushPromiseCache } -function flushUpdateQueue( - updateUrl: UpdateUrlFunction -): [URLSearchParams, null | unknown] { - const search = new URLSearchParams(location.search) +function flushUpdateQueue({ + updateUrl, + getSearchParamsSnapshot +}: Pick, 'updateUrl' | 'getSearchParamsSnapshot'>): [ + URLSearchParams, + null | unknown +] { + const search = getSearchParamsSnapshot() if (updateQueue.size === 0) { return [search, null] } diff --git a/packages/nuqs/src/useQueryState.ts b/packages/nuqs/src/useQueryState.ts index 08148783..338201b6 100644 --- a/packages/nuqs/src/useQueryState.ts +++ b/packages/nuqs/src/useQueryState.ts @@ -228,12 +228,8 @@ export function useQueryState( defaultValue: undefined } ) { - // Not reactive, but available on the server and on page load - const { - searchParams: initialSearchParams, - updateUrl, - rateLimitFactor = 1 - } = useAdapter() + const adapter = useAdapter() + const initialSearchParams = adapter.searchParams const queryRef = useRef(initialSearchParams?.get(key) ?? null) const [internalState, setInternalState] = useState(() => { const queuedQuery = getQueuedValue(key) @@ -302,18 +298,9 @@ export function useQueryState( }) // Sync all hooks state (including this one) emitter.emit(key, { state: newValue, query: queryRef.current }) - return scheduleFlushToURL(updateUrl, rateLimitFactor) + return scheduleFlushToURL(adapter) }, - [ - key, - history, - shallow, - scroll, - throttleMs, - startTransition, - updateUrl, - rateLimitFactor - ] + [key, history, shallow, scroll, throttleMs, startTransition, adapter] ) return [internalState ?? defaultValue ?? null, update] } diff --git a/packages/nuqs/src/useQueryStates.ts b/packages/nuqs/src/useQueryStates.ts index 849e7315..7b7775ad 100644 --- a/packages/nuqs/src/useQueryStates.ts +++ b/packages/nuqs/src/useQueryStates.ts @@ -90,11 +90,8 @@ export function useQueryStates( ), [stateKeys, urlKeys] ) - const { - searchParams: initialSearchParams, - updateUrl, - rateLimitFactor = 1 - } = useAdapter() + const adapter = useAdapter() + const initialSearchParams = adapter.searchParams const queryRef = useRef>({}) // Initialise the queryRef with the initial values if (Object.keys(queryRef.current).length !== Object.keys(keyMap).length) { @@ -243,7 +240,7 @@ export function useQueryStates( query: queryRef.current[urlKey] ?? null }) } - return scheduleFlushToURL(updateUrl, rateLimitFactor) + return scheduleFlushToURL(adapter) }, [ keyMap, @@ -253,8 +250,7 @@ export function useQueryStates( throttleMs, startTransition, resolvedUrlKeys, - updateUrl, - rateLimitFactor, + adapter, defaultValues ] )