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-search-param-state

v3.0.2

Published

A hook to synchronize React state with URL search params.

Readme

use-search-param-state

A hook to synchronize React state with URL search params.

version bundle size downloads per week package quality license dependencies

Docs for version 2.0.13 (the last version before version 3.0.0) can be viewed here

Basic usage

import { useSearchParamState } from "use-search-param-state";

function Demo() {
  const [counterState, setCounterState] = useSearchParamState("counter", 0);
}

or

import {
  useSearchParamState as _useSearchParamState,
  UseSearchParamStateOptions,
} from "use-search-param-state";
import { z } from "zod";

export function useSearchParamState<TVal>(
  searchParam: string,
  initialState: TVal,
  options: UseSearchParamStateOptions<TVal>,
) {
  return _useSearchParamState(searchParam, initialState, {
    sanitize: yourSanitizer,
    ...options,
  });
}

function Demo() {
  const schema = z.number();
  const [counterState, setCounterState] = useSearchParamState("counter", 0, {
    validate: z.number().parse,
  });
}

Explanation

On the first render, useSearchParamState will read from the counter URL search param. The counter search param is read using the return value of the useURLSearchParams hook.

If the window object is undefined (i.e on the server), useSearchParamState will use the serverSideURLSearchParams option. If serverSideURLSearchParams is also not provided, counterState will be returned initial state (i.e. 0).

If the counter search param does not exist (i.e. URLSearchParams.get returns null), counterState will be returned as the initial state, and the counter search param will be set to the initial state using the stringify option. If enableSetInitialSearchParam is set to false, the counter search param will not be set.

Once the counter search param is accessed, the raw string is passed to sanitize, the output of sanitize is passed to parse, and finally the output of parse is passed to validate. Note that useSearchParamState aims to return a parsed value, not a stringified value!

If sanitize, parse, or validate throw an error, the onError option is called and counterState will be returned as the initial state.

If none of sanitize, parse, and validate throw an error, counterState is returned as the sanitized, parsed, and validated value in the counter search param.


When setting the counter search param using setCounterState, the new state is stringified with the stringify option, and the URL is set using the pushURLSearchParams option. If setCounterState is called with the replace option, the replaceURLSearchParams option is used instead of the pushURLSearchParams option.

If deleteEmptySearchParam is true and isEmptySearchParam returns true, the search param will be deleted from the URL.

If stringify or pushURLSearchParams/replaceURLSearchParams throw an error, onError will be called and the URL will not be set.

Exports

import {
  useSearchParamState,
  getSearchParam,
  setSearchParam,
  UseSearchParamStateOptions,
  GetSearchParamOptions,
  SetSearchParamOptions,
} from "use-search-param-state";
import { useURLSearchParams } from "use-search-param-state/use-url-search-params";

useSearchParamState vs getSearchParam/setSearchParam

use-search-param-state exports three main utilities: useSearchParamState, getSearchParam, and setSearchParam.

The primary difference between useSearchParamState and getSearchParam/setSearchParam is that useSearchParamState is a hook, while getSearchParam/setSearchParam are functions. Because of this difference, useSearchParamState is able to react to URL changes and always return the up-to-date search param value, while getSearchParam provides a snapshot of the search param value at the time when it was called. Similarly, setSearchParam will not force getSearchParam to re-evaluate.

In React components, prefer to use useSearchParamState. When the search param needs to be read or set outside React, getSearchParam/setSearchParam are hook-less alternatives with the same API.

Option reference

interface OptionReference {
  /**
   * `onError` defaults to the following function:
   *
   * ```ts
   * export function defaultOnError(_e: unknown) {
   *   return;
   * }
   * ```
   *
   * @param `error` The error caught in one of `try` `catch` blocks.
   * @returns
   */
  onError?: (error: unknown) => void;

  /**
   * `sanitize` defaults to the following function:
   *
   * ```ts
   * const defaultSanitize = (unsanitized: string) => unsanitized;
   * ```
   *
   * If an error is thrown, `onError` is called and `useSearchParamState` returns the
   * default state. If using `getSearchParam`, `null` is returned.
   *
   * @param `unsanitized` The raw string pulled from the URL search param.
   * @returns The sanitized string.
   */
  sanitize?: (unsanitized: string) => string;

  /**
   *
   * `parse` defaults to the following function:
   *
   * ```ts
   * export function defaultParse<TVal>(unparsed: string): TVal {
   *   // JSON.parse errors on "undefined"
   *   if (unparsed === "undefined") return undefined as TVal;
   *
   *   try {
   *     return JSON.parse(unparsed) as TVal;
   *   } catch {
   *     return unparsed as TVal;
   *   }
   * }
   * ```
   *
   * If an error is thrown, `onError` is called and `useSearchParamState` returns the
   * default state. If using `getSearchParam`, `null` is returned.
   *
   * @param `unparsed` The result of `sanitize` is passed as `unparsed`.
   * @returns A parsed value of the type `TVal` i.e. the type of `initialState`.
   */
  parse?: (unparsed: string) => TVal;

  /**
   * `validate` is expected to validate and return the `unvalidated` argument passed to it
   * (presumably of type `TVal`), or throw an error.
   *
   * `validate` defaults to the following function:
   *
   * ```
   * const defaultValidate = <TVal>(unvalidated: unknown) => unvalidated as TVal;
   * ```
   *
   * If an error is thrown, `onError` is called and `useSearchParamState` returns the
   * default state. If using `getSearchParam`, `null` is returned.
   *
   * @param `unvalidated` The result of `parse` is passed as `unvalidated`.
   * @returns The `unvalidated` argument, now validated as of type `TVal`.
   */
  validate?: (unvalidated: unknown) => TVal;

  /**
   * When passed, `serverSideURLSearchParams` will be used when `window` is `undefined` to
   * access the URL search param. This is useful for generating content on the server,
   * i.e. with Next.js or Remix.
   *
   * `serverSideURLSearchParams` has no default.
   *
   * See MDN's documentation on the [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams)
   * object for more info.
   */
  serverSideURLSearchParams?: URLSearchParams;

  /**
   * When setting the search param, if `deleteEmptySearchParam` is set to `true` and
   * `isEmptySearchParam` returns `true`, the search param will be deleted from the URL.
   *
   * `deleteEmptySearchParam` defaults to `false`.
   */
  deleteEmptySearchParam?: boolean;

  /**
   * When setting the search param, if `deleteEmptySearchParam` is `true` and
   * `isEmptySearchParam` returns `true`, the search param will be deleted from the URL.
   *
   * `isEmptySearchParam` defaults to the following function:
   *
   * ```ts
   * export function defaultIsEmptySearchParam<TVal>(searchParamVal: TVal) {
   *  return (
   *    searchParamVal === null ||
   *    searchParamVal === undefined ||
   *    searchParamVal === ""
   *  );
   * }
   * ```
   *
   * @param `searchParamVal` When setting the state, the new state is passed as
   * `searchParamVal`.
   * @returns A boolean.
   */
  isEmptySearchParam?: (searchParamVal: TVal) => boolean;

  /**
   * `pushURLSearchParams` defaults to the following function:
   *
   * ```ts
   * function defaultPushURLSearchParams(urlSearchParams: URLSearchParams) {
   *  const maybeQuestionMark = urlSearchParams.toString().length ? "?" : "";
   *  window.history.pushState(
   *    {},
   *    "",
   *    `${window.location.pathname}${maybeQuestionMark}${urlSearchParams.toString()}`,
   *  );
   *}
   * ```
   *
   * @param `urlSearchParams` The `urlSearchParams` to set
   * returned by `useSearchParamState`.
   * @returns
   */
  pushURLSearchParams?: (urlSearchParams: URLSearchParams) => void;

  /**
   * `replaceURLSearchParams` defaults to the following function:
   *
   * ```ts
   * function defaultReplaceURLSearchParams(urlSearchParams: URLSearchParams) {
   *   const maybeQuestionMark = urlSearchParams.toString().length ? "?" : "";
   *   window.history.replaceState(
   *     {},
   *     "",
   *     `${window.location.pathname}${maybeQuestionMark}${urlSearchParams.toString()}`,
   *   );
   * }
   * ```
   *
   * @param `urlSearchParams` The `urlSearchParams` to set
   * returned by `useSearchParamState` with the `replace` option as `true`.
   * @returns
   */
  replaceURLSearchParams?: (urlSearchParams: URLSearchParams) => void;

  /**
   * `stringify` defaults to the following function:
   *
   * ```ts
   * export function defaultStringify<TVal>(valToStringify: TVal) {
   *   // avoid wrapping strings in quotes
   *   if (typeof valToStringify === "string") return valToStringify;
   *   return JSON.stringify(valToStringify);
   * }
   * ```
   *
   * @param `valToStringify` The search param to stringify before setting it in the URL.
   * @returns The stringified search param.
   */
  stringify?: (valToStringify: TVal) => string;

  /**
   * A React hook to return a URLSearchParams object representing the current search
   * params. Note that this hook _must_ return a referentially stable value.
   *
   * `useURLSearchParams` defaults to an internal hook.
   *
   * See MDN's documentation on the [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams)
   * object for more info.
   */
  useURLSearchParams?: () => URLSearchParams;

  /**
   * If the search param state resolves to `null`, the URL is replaced with the search
   * param set as the `initialState` option.
   *
   * `enableSetInitialSearchParam` defaults to `true`
   */
  enableSetInitialSearchParam?: boolean;

  /**
   * A function to return the current URL object.
   *
   * `getURLSearchParams` defaults to the following function
   *
   * ```ts
   * const defaultGetURLSearchParams = () => new URLSearchParams(window.location.search);
   * ```
   *
   * See MDN's documentation on the [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams)
   * object for more info.
   */
  getURLSearchParams?: () => URLSearchParams;

  /**
   * If `true`, when setting the search param, the updated URL will replace the top item
   * in the history stack instead of pushing to it.
   *
   * See MDN's documentation on [replaceState](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState)
   * for more info.
   */
  replace?: boolean;
}

useSearchParamState options

interface UseSearchParamStateOptions<TVal> {
  onError?: (error: unknown) => void;

  // read options
  serverSideURLSearchParams?: URLSearchParams;
  sanitize?: (unsanitized: string) => string;
  parse?: (unparsed: string) => TVal;
  validate?: (unvalidated: unknown) => TVal;

  // write options
  stringify?: (valToStringify: TVal) => string;
  deleteEmptySearchParam?: boolean;
  isEmptySearchParam?: (searchParamVal: TVal) => boolean;
  pushURLSearchParams?: (urlSearchParams: URLSearchParams) => void;
  replaceURLSearchParams?: (urlSearchParams: URLSearchParams) => void;

  // hook-only options
  enableSetInitialSearchParam?: boolean;
  useURLSearchParams?: () => URLSearchParams;
}

getSearchParam options

interface GetSearchParamOptions<TVal> {
  onError?: (error: unknown) => void;

  // read options
  serverSideURLSearchParams?: URLSearchParams;
  sanitize?: (unsanitized: string) => string;
  parse?: (unparsed: string) => TVal;
  validate?: (unvalidated: unknown) => TVal;

  // function-only options
  getURLSearchParams?: () => URLSearchParams;
}

setSearchParam options

interface SetSearchParamOptions<TVal> {
  onError?: (error: unknown) => void;

  // write options
  stringify?: (valToStringify: TVal) => string;
  deleteEmptySearchParam?: boolean;
  isEmptySearchParam?: (searchParamVal: TVal) => boolean;
  pushURLSearchParams?: (urlSearchParams: URLSearchParams) => void;
  replaceURLSearchParams?: (urlSearchParams: URLSearchParams) => void;

  // function-only options
  getURLSearchParams?: () => URLSearchParams;
  replace?: boolean;
}

Recipes

Kitchen sink

/*
 * - `parse`/`stringify` the `list` serach param as `?list=1_2_3` instead of `?list=%5B1%2C2%2C3%5D`
 * - `validate` the parsed list as an array of numbers
 * - delete the `list` search param when the current list has a length of 0
 */
const [list, setList] = useSearchParamState<number[]>("list", [], {
  deleteEmptySearchParam: true,
  isEmptySearchParam: (currList) => {
    return currList.length === 0;
  },
  parse: (unparsed) => {
    return unparsed.split("_").map((val) => Number(val));
  },
  validate: z.array(z.number()).parse,
  stringify: (currList) => {
    return currList.join("_");
  },
});

Hooking into the Next.js Pages router

import React from "react";
import {
  useSearchParamState as _useSearchParamState,
  UseSearchParamStateOptions,
} from "use-search-param-state";
import { useRouter } from "next/router";
import { NextPageContext, InferGetServerSidePropsType } from "next";
import { stringify } from "querystring";

export function useSearchParamState<TVal>(
  searchParam: string,
  initialState: TVal,
  options: UseSearchParamStateOptions<TVal> = {},
) {
  const router = useRouter();

  function pushURLSearchParams(urlSearchParams: URLSearchParams) {
    const maybeQuestionmark = urlSearchParams.toString().length ? "?" : "";
    router.push(
      `${router.pathname}${maybeQuestionmark}${urlSearchParams.toString()}`,
      undefined,
      { shallow: true },
    );
  }

  function replaceURLSearchParams(urlSearchParams: URLSearchParams) {
    const maybeQuestionmark = urlSearchParams.toString().length ? "?" : "";
    router.replace(
      `${router.pathname}${maybeQuestionmark}${urlSearchParams.toString()}`,
      undefined,
      { shallow: true },
    );
  }

  return _useSearchParamState(searchParam, initialState, {
    pushURLSearchParams,
    replaceURLSearchParams,
    ...options,
  });
}

function Page({
  serverSideSearchString,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  const [count, setCount] = useSearchParamState("count", 0, {
    serverSideURLSearchParams: new URLSearchParams(serverSideSearchString),
  });
}

export function getServerSideProps(ctx: NextPageContext) {
  const dummyURL = new URL(ctx.req?.url ?? "", "http://a.com");
  const serverSideSearchString = dummyURL.search;

  return {
    props: {
      serverSideSearchString,
    },
  };
}