npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

use-query-guard

v1.1.1

Published

A router-agnostic query-string management hook for React with Zod validation

Readme

useQueryGuard

npm version GitHub

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.history by default. Inject a custom adapter to integrate any router.
  • Type‑safe validation with Zod Pass a z.object() schema and data is 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 type string | number | boolean | null.
  • null to delete keys undefined means "leave unchanged"; null removes the key from the URL.
  • SSR friendly All browser APIs wrapped in typeof window checks.

Installation

npm install use-query-guard zod
# or
pnpm add use-query-guard zod
# or
yarn add use-query-guard zod

Alternative: Copy the hook code directly to your project:

# Copy the hook code to src/hooks/useSearchParams.ts

Dependencies: 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