Skip to content

Commit

Permalink
feat: Abstract reading from location.search in adapters interface (#800)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
franky47 authored Dec 11, 2024
1 parent a622d9b commit 1635863
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 36 deletions.
1 change: 1 addition & 0 deletions packages/nuqs/src/adapters/defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export type UseAdapterHook = () => AdapterInterface
export type AdapterInterface = {
searchParams: URLSearchParams
updateUrl: UpdateUrlFunction
getSearchParamsSnapshot?: () => URLSearchParams
rateLimitFactor?: number
}
3 changes: 3 additions & 0 deletions packages/nuqs/src/adapters/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export function NuqsTestingAdapter({
options
})
},
getSearchParamsSnapshot() {
return new URLSearchParams(props.searchParams)
},
rateLimitFactor: props.rateLimitFactor ?? 0
})
return createElement(
Expand Down
33 changes: 22 additions & 11 deletions packages/nuqs/src/update-queue.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -76,15 +76,19 @@ export function enqueueQueryStringUpdate<Value>(
*
* @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<URLSearchParams>((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
Expand All @@ -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 {
Expand Down Expand Up @@ -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<Required<AdapterInterface>, 'updateUrl' | 'getSearchParamsSnapshot'>): [
URLSearchParams,
null | unknown
] {
const search = getSearchParamsSnapshot()
if (updateQueue.size === 0) {
return [search, null]
}
Expand Down
21 changes: 4 additions & 17 deletions packages/nuqs/src/useQueryState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,8 @@ export function useQueryState<T = string>(
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<string | null>(initialSearchParams?.get(key) ?? null)
const [internalState, setInternalState] = useState<T | null>(() => {
const queuedQuery = getQueuedValue(key)
Expand Down Expand Up @@ -302,18 +298,9 @@ export function useQueryState<T = string>(
})
// 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]
}
Expand Down
12 changes: 4 additions & 8 deletions packages/nuqs/src/useQueryStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,8 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
),
[stateKeys, urlKeys]
)
const {
searchParams: initialSearchParams,
updateUrl,
rateLimitFactor = 1
} = useAdapter()
const adapter = useAdapter()
const initialSearchParams = adapter.searchParams
const queryRef = useRef<Record<string, string | null>>({})
// Initialise the queryRef with the initial values
if (Object.keys(queryRef.current).length !== Object.keys(keyMap).length) {
Expand Down Expand Up @@ -243,7 +240,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
query: queryRef.current[urlKey] ?? null
})
}
return scheduleFlushToURL(updateUrl, rateLimitFactor)
return scheduleFlushToURL(adapter)
},
[
keyMap,
Expand All @@ -253,8 +250,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
throttleMs,
startTransition,
resolvedUrlKeys,
updateUrl,
rateLimitFactor,
adapter,
defaultValues
]
)
Expand Down

0 comments on commit 1635863

Please sign in to comment.