use-query-guard
v1.1.1
Published
A router-agnostic query-string management hook for React with Zod validation
Maintainers
Readme
useQueryGuard
A router‑agnostic query‑string management hook for React. Read, validate, and update URL parameters in React‑Router, Next.js, Remix, or any vanilla SPA with one shared API.
Features
- Router‑free core
Uses
window.historyby default. Inject a custom adapter to integrate any router. - Type‑safe validation with Zod
Pass a
z.object()schema anddatais returned with that exact type. Automatic coercion ("42"→42, etc.) included. - Safe updates
updateParams()only accepts keys declared in the schema (if provided) and values of typestring | number | boolean | null. nullto delete keysundefinedmeans "leave unchanged";nullremoves the key from the URL.- SSR friendly
All browser APIs wrapped in
typeof windowchecks.
Installation
npm install use-query-guard zod
# or
pnpm add use-query-guard zod
# or
yarn add use-query-guard zodAlternative: Copy the hook code directly to your project:
# Copy the hook code to src/hooks/useSearchParams.tsDependencies: React 18+ and Zod 3+ only.
Quick start
import { useQueryGuard } from 'use-query-guard'
import { z } from 'zod'
const schema = z.object({
page: z.number(),
q: z.string().optional(),
vip: z.boolean(),
})
export default function Products() {
const { data, isReady, isError, updateParams } =
useQueryGuard({ resolver: schema })
if (!isReady) return null
if (isError) return <p>Invalid query string</p>
return (
<>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={() => updateParams({ page: (data.page ?? 1) + 1 })}>
Next page
</button>
<button onClick={() => updateParams({ q: null })}>
Clear search
</button>
</>
)
}Returned object
| key | type | description |
| --- | --- | --- |
| data | without resolver: Record<string, string \| undefined>with resolver: { [K in keyof Schema]?: Type } | Parsed & coerced data. All schema keys exist but values may be undefined. |
| isReady | boolean | true after the first read of the URL. |
| isError | boolean | true if safeParse failed. |
| updateParams | (params) => void | Add / update / delete query keys. |
updateParams() value rules
| value | effect |
| --- | --- |
| string / number / boolean | Converted with String() and set in URL |
| null or '' | Deletes the key |
| omit key | Leaves that key unchanged |
Adapter example (Next.js)
import { useRouter, useSearchParams as nextSP } from 'next/navigation'
import type { QueryGuardAdapter } from 'use-query-guard'
export const nextAdapter = (): QueryGuardAdapter => {
const router = useRouter()
const current = nextSP()
return {
getSearch: () => '?' + current.toString(),
setSearch: qs => router.push(qs ? '?' + qs : '?'),
subscribe: cb => {
window.addEventListener('popstate', cb)
return () => window.removeEventListener('popstate', cb)
},
}
}
// Usage
const { data } = useQueryGuard({
resolver: schema,
adapter: nextAdapter(),
})API (excerpt)
function useQueryGuard<
S extends z.ZodObject<z.ZodRawShape> | undefined = undefined
>(options?: {
resolver?: S
preprocess?: (
key: S extends z.ZodObject<any> ? keyof z.infer<S> & string : string,
value: string
) => string
adapter?: QueryGuardAdapter
mode?: 'pick' | 'strict'
}): {
data: S extends z.ZodObject<any>
? { [K in keyof z.infer<S>]?: z.infer<S>[K] }
: Record<string, string | undefined>
isReady: boolean
isError: boolean
updateParams: (p: UpdateArgs<S>) => void
}オプション: mode
mode: 'pick'(デフォルト): スキーマに一致するキーだけ部分的にパースし、1つでも不正値があればisError: trueになるが、他の値は取得できるmode: 'strict': 全てのキーがスキーマに完全一致しないとisError: trueとなり、dataも空になる
例
// pickモード(デフォルト)
const { data, isError } = useQueryGuard({
resolver: schema,
mode: 'pick',
})
// strictモード
const { data, isError } = useQueryGuard({
resolver: schema,
mode: 'strict',
})License
MIT
