Skip to content

Commit

Permalink
fix: Handle dynamic default values in useQueryStates
Browse files Browse the repository at this point in the history
  • Loading branch information
franky47 committed Nov 13, 2024
1 parent 3d48c05 commit dd19510
Showing 1 changed file with 40 additions and 14 deletions.
54 changes: 40 additions & 14 deletions packages/nuqs/src/useQueryStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type Values<T extends UseQueryStatesKeysMap> = {
? NonNullable<ReturnType<T[K]['parse']>>
: ReturnType<T[K]['parse']> | null
}
type NullableValues<T extends UseQueryStatesKeysMap> = Nullable<Values<T>>

type UpdaterFn<T extends UseQueryStatesKeysMap> = (
old: Values<T>
Expand Down Expand Up @@ -80,7 +81,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
urlKeys = defaultUrlKeys
}: Partial<UseQueryStatesOptions<KeyMap>> = {}
): UseQueryStatesReturn<KeyMap> {
type V = Values<KeyMap>
type V = NullableValues<KeyMap>
const stateKeys = Object.keys(keyMap).join(',')
const resolvedUrlKeys = useMemo(
() =>
Expand All @@ -99,6 +100,17 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
if (Object.keys(queryRef.current).length !== Object.keys(keyMap).length) {
queryRef.current = Object.fromEntries(initialSearchParams?.entries() ?? [])
}
const defaultValues = useMemo(
() =>
Object.fromEntries(
Object.keys(keyMap).map(key => [key, keyMap[key]!.defaultValue ?? null])
) as Values<KeyMap>,
[
Object.values(keyMap)
.map(({ defaultValue }) => defaultValue)
.join(',')
]
)

const [internalState, setInternalState] = useState<V>(() => {
const source = initialSearchParams ?? new URLSearchParams()
Expand Down Expand Up @@ -137,7 +149,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
}
const handlers = Object.keys(keyMap).reduce(
(handlers, stateKey) => {
handlers[stateKey as keyof V] = ({
handlers[stateKey as keyof KeyMap] = ({
state,
query
}: CrossHookSyncPayload) => {
Expand All @@ -147,7 +159,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
// for the subsequent setState to pick it up.
stateRef.current = {
...stateRef.current,
[stateKey as keyof V]: state ?? defaultValue ?? null
[stateKey as keyof KeyMap]: state ?? defaultValue ?? null
}
queryRef.current[urlKey] = query
debug(
Expand All @@ -162,7 +174,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
}
return handlers
},
{} as Record<keyof V, (payload: CrossHookSyncPayload) => void>
{} as Record<keyof KeyMap, (payload: CrossHookSyncPayload) => void>
)

for (const stateKey of Object.keys(keyMap)) {
Expand All @@ -183,7 +195,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
(stateUpdater, callOptions = {}) => {
const newState: Partial<Nullable<KeyMap>> =
typeof stateUpdater === 'function'
? stateUpdater(stateRef.current)
? stateUpdater(applyDefaultValues(stateRef.current, defaultValues))
: stateUpdater === null
? (Object.fromEntries(
Object.keys(keyMap).map(key => [key, null])
Expand Down Expand Up @@ -241,10 +253,16 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
startTransition,
resolvedUrlKeys,
updateUrl,
rateLimitFactor
rateLimitFactor,
defaultValues
]
)
return [internalState, update]

const outputState = useMemo(
() => applyDefaultValues(internalState, defaultValues),
[internalState, defaultValues]
)
return [outputState, update]
}

// --
Expand All @@ -254,26 +272,34 @@ function parseMap<KeyMap extends UseQueryStatesKeysMap>(
urlKeys: Partial<Record<keyof KeyMap, string>>,
searchParams: URLSearchParams,
cachedQuery?: Record<string, string | null>,
cachedState?: Values<KeyMap>
) {
cachedState?: NullableValues<KeyMap>
): NullableValues<KeyMap> {
return Object.keys(keyMap).reduce((obj, stateKey) => {
const urlKey = urlKeys?.[stateKey] ?? stateKey
const { defaultValue, parse } = keyMap[stateKey]!
const { parse } = keyMap[stateKey]!
const queuedQuery = getQueuedValue(urlKey)
const query =
queuedQuery === undefined
? (searchParams?.get(urlKey) ?? null)
: queuedQuery
if (cachedQuery && cachedState && cachedQuery[urlKey] === query) {
obj[stateKey as keyof KeyMap] =
cachedState[stateKey] ?? defaultValue ?? null
obj[stateKey as keyof KeyMap] = cachedState[stateKey] ?? null
return obj
}
const value = query === null ? null : safeParse(parse, query, stateKey)
obj[stateKey as keyof KeyMap] = value ?? defaultValue ?? null
obj[stateKey as keyof KeyMap] = value ?? null
if (cachedQuery) {
cachedQuery[urlKey] = query
}
return obj
}, {} as Values<KeyMap>)
}, {} as NullableValues<KeyMap>)
}

function applyDefaultValues<KeyMap extends UseQueryStatesKeysMap>(
state: NullableValues<KeyMap>,
defaults: Partial<Values<KeyMap>>
) {
return Object.fromEntries(
Object.keys(state).map(key => [key, state[key] ?? defaults[key] ?? null])
) as Values<KeyMap>
}

0 comments on commit dd19510

Please sign in to comment.