nuqs-zod-adapter
Version:
A Nuqs adapter to safely bind Zod schemas with query parameters using type safety, debouncing and auto-reset support.
92 lines (77 loc) • 2.41 kB
text/typescript
import {
useQueryState,
parseAsString,
parseAsInteger,
parseAsBoolean,
type Options as BaseOptions
} from 'nuqs'
import { useDebouncedCallback } from 'use-debounce'
import { useEffect, useState } from 'react'
import { z } from 'zod'
type Options = BaseOptions & {
delay?: number
resetKeys?: string[]
}
export function useSafeQueryStateFromZod<S extends z.ZodTypeAny>(
key: string,
schema: S,
options?: Options
): [
z.infer<S>,
(value: z.infer<S>) => void
] {
let baseSchema = schema
let defaultValue: any = undefined
if (schema._def.typeName === 'ZodDefault') {
defaultValue = schema._def.defaultValue()
baseSchema = schema._def.innerType
}
const typeName = baseSchema._def.typeName
let parser: any
switch (typeName) {
case 'ZodString':
case 'ZodEnum':
case 'ZodNativeEnum':
case 'ZodLiteral':
case 'ZodUnion':
parser = parseAsString.withDefault(defaultValue)
break
case 'ZodNumber':
parser = parseAsInteger.withDefault(defaultValue)
break
case 'ZodBoolean':
parser = parseAsBoolean.withDefault(defaultValue)
break
case 'ZodEffects':
if (baseSchema._def.schema?._def?.typeName === 'ZodNumber') {
parser = parseAsInteger.withDefault(defaultValue)
} else {
parser = parseAsString.withDefault(defaultValue)
}
break
default:
}
const [rawValue, setRaw] = useQueryState<z.infer<S>>(key, {
...parser,
...options
})
const delay = options?.delay
const resetKeys = options?.resetKeys ?? []
if (!delay) return [rawValue ?? defaultValue, setRaw]
const [internalValue, setInternalValue] = useState<z.infer<S> | null>(rawValue)
const debouncedSet = useDebouncedCallback((v: z.infer<S> | null) => {
setRaw(v)
for (const rk of resetKeys) {
const [, resetSetter] = useQueryState(rk, parseAsString.withDefault(''))
resetSetter(null)
}
}, delay)
const set = (v: z.infer<S> | null) => {
setInternalValue(v)
debouncedSet(v)
}
useEffect(() => {
setInternalValue(rawValue)
}, [rawValue])
return [internalValue ?? defaultValue as z.infer<S>, set]
}