Skip to content

Commit 36e1d88

Browse files
committed
fix: Handle dynamic default values in useQueryStates
1 parent d2da916 commit 36e1d88

File tree

1 file changed

+40
-14
lines changed

1 file changed

+40
-14
lines changed

packages/nuqs/src/useQueryStates.ts

+40-14
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type Values<T extends UseQueryStatesKeysMap> = {
4040
? NonNullable<ReturnType<T[K]['parse']>>
4141
: ReturnType<T[K]['parse']> | null
4242
}
43+
type NullableValues<T extends UseQueryStatesKeysMap> = Nullable<Values<T>>
4344

4445
type UpdaterFn<T extends UseQueryStatesKeysMap> = (
4546
old: Values<T>
@@ -80,7 +81,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
8081
urlKeys = defaultUrlKeys
8182
}: Partial<UseQueryStatesOptions<KeyMap>> = {}
8283
): UseQueryStatesReturn<KeyMap> {
83-
type V = Values<KeyMap>
84+
type V = NullableValues<KeyMap>
8485
const stateKeys = Object.keys(keyMap).join(',')
8586
const resolvedUrlKeys = useMemo(
8687
() =>
@@ -99,6 +100,17 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
99100
if (Object.keys(queryRef.current).length !== Object.keys(keyMap).length) {
100101
queryRef.current = Object.fromEntries(initialSearchParams?.entries() ?? [])
101102
}
103+
const defaultValues = useMemo(
104+
() =>
105+
Object.fromEntries(
106+
Object.keys(keyMap).map(key => [key, keyMap[key]!.defaultValue ?? null])
107+
) as Values<KeyMap>,
108+
[
109+
Object.values(keyMap)
110+
.map(({ defaultValue }) => defaultValue)
111+
.join(',')
112+
]
113+
)
102114

103115
const [internalState, setInternalState] = useState<V>(() => {
104116
const source = initialSearchParams ?? new URLSearchParams()
@@ -137,7 +149,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
137149
}
138150
const handlers = Object.keys(keyMap).reduce(
139151
(handlers, stateKey) => {
140-
handlers[stateKey as keyof V] = ({
152+
handlers[stateKey as keyof KeyMap] = ({
141153
state,
142154
query
143155
}: CrossHookSyncPayload) => {
@@ -147,7 +159,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
147159
// for the subsequent setState to pick it up.
148160
stateRef.current = {
149161
...stateRef.current,
150-
[stateKey as keyof V]: state ?? defaultValue ?? null
162+
[stateKey as keyof KeyMap]: state ?? defaultValue ?? null
151163
}
152164
queryRef.current[urlKey] = query
153165
debug(
@@ -162,7 +174,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
162174
}
163175
return handlers
164176
},
165-
{} as Record<keyof V, (payload: CrossHookSyncPayload) => void>
177+
{} as Record<keyof KeyMap, (payload: CrossHookSyncPayload) => void>
166178
)
167179

168180
for (const stateKey of Object.keys(keyMap)) {
@@ -183,7 +195,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
183195
(stateUpdater, callOptions = {}) => {
184196
const newState: Partial<Nullable<KeyMap>> =
185197
typeof stateUpdater === 'function'
186-
? stateUpdater(stateRef.current)
198+
? stateUpdater(applyDefaultValues(stateRef.current, defaultValues))
187199
: stateUpdater === null
188200
? (Object.fromEntries(
189201
Object.keys(keyMap).map(key => [key, null])
@@ -241,10 +253,16 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
241253
startTransition,
242254
resolvedUrlKeys,
243255
updateUrl,
244-
rateLimitFactor
256+
rateLimitFactor,
257+
defaultValues
245258
]
246259
)
247-
return [internalState, update]
260+
261+
const outputState = useMemo(
262+
() => applyDefaultValues(internalState, defaultValues),
263+
[internalState, defaultValues]
264+
)
265+
return [outputState, update]
248266
}
249267

250268
// --
@@ -254,26 +272,34 @@ function parseMap<KeyMap extends UseQueryStatesKeysMap>(
254272
urlKeys: Partial<Record<keyof KeyMap, string>>,
255273
searchParams: URLSearchParams,
256274
cachedQuery?: Record<string, string | null>,
257-
cachedState?: Values<KeyMap>
258-
) {
275+
cachedState?: NullableValues<KeyMap>
276+
): NullableValues<KeyMap> {
259277
return Object.keys(keyMap).reduce((obj, stateKey) => {
260278
const urlKey = urlKeys?.[stateKey] ?? stateKey
261-
const { defaultValue, parse } = keyMap[stateKey]!
279+
const { parse } = keyMap[stateKey]!
262280
const queuedQuery = getQueuedValue(urlKey)
263281
const query =
264282
queuedQuery === undefined
265283
? (searchParams?.get(urlKey) ?? null)
266284
: queuedQuery
267285
if (cachedQuery && cachedState && cachedQuery[urlKey] === query) {
268-
obj[stateKey as keyof KeyMap] =
269-
cachedState[stateKey] ?? defaultValue ?? null
286+
obj[stateKey as keyof KeyMap] = cachedState[stateKey] ?? null
270287
return obj
271288
}
272289
const value = query === null ? null : safeParse(parse, query, stateKey)
273-
obj[stateKey as keyof KeyMap] = value ?? defaultValue ?? null
290+
obj[stateKey as keyof KeyMap] = value ?? null
274291
if (cachedQuery) {
275292
cachedQuery[urlKey] = query
276293
}
277294
return obj
278-
}, {} as Values<KeyMap>)
295+
}, {} as NullableValues<KeyMap>)
296+
}
297+
298+
function applyDefaultValues<KeyMap extends UseQueryStatesKeysMap>(
299+
state: NullableValues<KeyMap>,
300+
defaults: Partial<Values<KeyMap>>
301+
) {
302+
return Object.fromEntries(
303+
Object.keys(state).map(key => [key, state[key] ?? defaults[key] ?? null])
304+
) as Values<KeyMap>
279305
}

0 commit comments

Comments
 (0)