Skip to content

Commit 0a61c35

Browse files
committed
feat: Abstract reading from location.search in adapters interface
This is mainly for the testing adapter to only work on the provided set of initial search params without needing to stub/mock location.search. But it also opens the door for adapters to define new storage locations, like hash/fragment or even localStorage.
1 parent bf0e2cb commit 0a61c35

File tree

5 files changed

+34
-36
lines changed

5 files changed

+34
-36
lines changed

packages/nuqs/src/adapters/defs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export type UseAdapterHook = () => AdapterInterface
1212
export type AdapterInterface = {
1313
searchParams: URLSearchParams
1414
updateUrl: UpdateUrlFunction
15+
getSearchParamsSnapshot?: () => URLSearchParams
1516
rateLimitFactor?: number
1617
}

packages/nuqs/src/adapters/testing.ts

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export function NuqsTestingAdapter({
3636
options
3737
})
3838
},
39+
getSearchParamsSnapshot() {
40+
return new URLSearchParams(props.searchParams)
41+
},
3942
rateLimitFactor: props.rateLimitFactor ?? 0
4043
})
4144
return createElement(

packages/nuqs/src/update-queue.ts

+22-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { UpdateUrlFunction } from './adapters/defs'
1+
import type { AdapterInterface } from './adapters/defs'
22
import { debug } from './debug'
33
import type { Options } from './defs'
44
import { error } from './errors'
@@ -76,15 +76,19 @@ export function enqueueQueryStringUpdate<Value>(
7676
*
7777
* @returns a Promise to the URLSearchParams that have been applied.
7878
*/
79-
export function scheduleFlushToURL(
80-
updateUrl: UpdateUrlFunction,
81-
rateLimitFactor: number
82-
) {
79+
export function scheduleFlushToURL({
80+
getSearchParamsSnapshot = () => new URLSearchParams(location.search),
81+
updateUrl,
82+
rateLimitFactor = 1
83+
}: Pick<
84+
AdapterInterface,
85+
'updateUrl' | 'getSearchParamsSnapshot' | 'rateLimitFactor'
86+
>) {
8387
if (flushPromiseCache === null) {
8488
flushPromiseCache = new Promise<URLSearchParams>((resolve, reject) => {
8589
if (!Number.isFinite(queueOptions.throttleMs)) {
8690
debug('[nuqs queue] Skipping flush due to throttleMs=Infinity')
87-
resolve(new URLSearchParams(location.search))
91+
resolve(getSearchParamsSnapshot())
8892
// Let the promise be returned before clearing the cached value
8993
setTimeout(() => {
9094
flushPromiseCache = null
@@ -93,7 +97,10 @@ export function scheduleFlushToURL(
9397
}
9498
function flushNow() {
9599
lastFlushTimestamp = performance.now()
96-
const [search, error] = flushUpdateQueue(updateUrl)
100+
const [search, error] = flushUpdateQueue({
101+
updateUrl,
102+
getSearchParamsSnapshot
103+
})
97104
if (error === null) {
98105
resolve(search)
99106
} else {
@@ -129,10 +136,14 @@ export function scheduleFlushToURL(
129136
return flushPromiseCache
130137
}
131138

132-
function flushUpdateQueue(
133-
updateUrl: UpdateUrlFunction
134-
): [URLSearchParams, null | unknown] {
135-
const search = new URLSearchParams(location.search)
139+
function flushUpdateQueue({
140+
updateUrl,
141+
getSearchParamsSnapshot
142+
}: Pick<Required<AdapterInterface>, 'updateUrl' | 'getSearchParamsSnapshot'>): [
143+
URLSearchParams,
144+
null | unknown
145+
] {
146+
const search = getSearchParamsSnapshot()
136147
if (updateQueue.size === 0) {
137148
return [search, null]
138149
}

packages/nuqs/src/useQueryState.ts

+4-17
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,8 @@ export function useQueryState<T = string>(
228228
defaultValue: undefined
229229
}
230230
) {
231-
// Not reactive, but available on the server and on page load
232-
const {
233-
searchParams: initialSearchParams,
234-
updateUrl,
235-
rateLimitFactor = 1
236-
} = useAdapter()
231+
const adapter = useAdapter()
232+
const initialSearchParams = adapter.searchParams
237233
const queryRef = useRef<string | null>(initialSearchParams?.get(key) ?? null)
238234
const [internalState, setInternalState] = useState<T | null>(() => {
239235
const queuedQuery = getQueuedValue(key)
@@ -302,18 +298,9 @@ export function useQueryState<T = string>(
302298
})
303299
// Sync all hooks state (including this one)
304300
emitter.emit(key, { state: newValue, query: queryRef.current })
305-
return scheduleFlushToURL(updateUrl, rateLimitFactor)
301+
return scheduleFlushToURL(adapter)
306302
},
307-
[
308-
key,
309-
history,
310-
shallow,
311-
scroll,
312-
throttleMs,
313-
startTransition,
314-
updateUrl,
315-
rateLimitFactor
316-
]
303+
[key, history, shallow, scroll, throttleMs, startTransition, adapter]
317304
)
318305
return [internalState ?? defaultValue ?? null, update]
319306
}

packages/nuqs/src/useQueryStates.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,8 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
9090
),
9191
[stateKeys, urlKeys]
9292
)
93-
const {
94-
searchParams: initialSearchParams,
95-
updateUrl,
96-
rateLimitFactor = 1
97-
} = useAdapter()
93+
const adapter = useAdapter()
94+
const initialSearchParams = adapter.searchParams
9895
const queryRef = useRef<Record<string, string | null>>({})
9996
// Initialise the queryRef with the initial values
10097
if (Object.keys(queryRef.current).length !== Object.keys(keyMap).length) {
@@ -243,7 +240,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
243240
query: queryRef.current[urlKey] ?? null
244241
})
245242
}
246-
return scheduleFlushToURL(updateUrl, rateLimitFactor)
243+
return scheduleFlushToURL(adapter)
247244
},
248245
[
249246
keyMap,
@@ -253,8 +250,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
253250
throttleMs,
254251
startTransition,
255252
resolvedUrlKeys,
256-
updateUrl,
257-
rateLimitFactor,
253+
adapter,
258254
defaultValues
259255
]
260256
)

0 commit comments

Comments
 (0)