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 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-search-param

v2.3.0

Published

A React hook to safely and easily read from URL search params.

Downloads

19

Readme

use-search-param

A React hook to safely and easily read from URL search params.

version bundle size downloads per week package quality license dependencies

Docs for version 1.4.4 (the last version before version 2.0.0) can be viewed here

Basic usage

import { useSearchParam } from "use-search-param";

function Demo() {
  const counter = useSearchParam<number>("c");
  // ...
}

or

import { useSearchParam } from "use-search-param";
import { z } from "zod";

function Demo() {
  const counter =
    useSearchParam("c", {
      validate: z.number().parse,
    }) ?? 0;
  // ...
}

Explanation

On the first render, useSearchParam will read from the c URL search param.

By default, the c search param is read using window.location.search. If the window object is undefined, useSearchParam will use the serverSideSearchParams instead to read from the URL. If serverSideSearchParams is also not provided, counter will be set to null.

If the c search param does not exist (i.e. URLSearchParams.get returns null), counter will be set to null.

Once the c 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 useSearchParam aims to return a parsed value, not a stringified value!

If sanitize, parse, or validate throw an error, the onError option is called, and counterVal is set to null. Additionally if validate returns null, counter will be set to null.

Otherwise, counter is set to the sanitized, parsed, and validated value in the c search param.

Options

useSearchParam accepts the following options:

interface Options<TVal> {
  sanitize?: (unsanitized: string) => string;
  parse?: (unparsed: string) => TVal;
  validate?: (unvalidated: unknown) => TVal | null;
  onError?: (error: unknown) => void;
  serverSideSearchParams?: string;
}

Note that sanitize, parse, and validate run in the following order:

// simplified
const rawSearchParam = new URLSearchParams(window.location.search).get(
  searchParamKey,
);
const sanitized = options.sanitize(rawSearchParam);
const parsed = options.parse(sanitized);
const validated = options.validate(parsed);

return validated;

sanitize

A function with the following type: (unsanitized: string) => string.

sanitize is called with the raw string pulled from the URL search param.

sanitize can be passed directly to useSearchParam, or to buildUseSearchParam. When a sanitize option is passed to both, only the sanitize passed to useSearchParam will be called.

sanitize has no default value.

parse

A function with the following type: (unparsed: string) => TValue.

The result of sanitize is passed as the unparsed argument to parse.

parse can be passed directly to useSearchParam, or to buildUseSearchParam. When a parse option is passed to both, only the parse passed to useSearchParam will be called.

parse defaults to the following function:

function defaultParse(unparsed: string) {
  // JSON.parse errors on "undefined"
  if (unparsed === "undefined") return undefined;

  // Number parses "" to 0
  if (unparsed === "") return "";

  // Number coerces bigints to numbers
  const maybeNum = Number(unparsed);
  if (!Number.isNaN(maybeNum)) return maybeNum;

  try {
    return JSON.parse(unparsed);
  } catch {
    return unparsed;
  }
}

validate

A function with the following type: (unvalidated: unknown) => TVal | null.

The result of parse is passed as the unvalidated argument to validate.

validate is expected to validate and return the unvalidated argument passed to it (presumably of type TVal), explicitly return null, or throw an error. If an error is thrown, onError is called and useSearchParam returns null.

validate has no default value.

onError

A function with the following type: (error: unknown) => void.

Most actions in useSearchParam are wrapped in a try catch block - onError is called whenever the catch block is reached. This includes situations when sanitize, parse, or validate throw an error.

onError can be passed directly to useSearchParam, or to buildUseSearchParam. When an onError option is passed to both, both the functions will be called.

serverSideSearchParams

A value of type string - any valid string input to the URLSearchParams constructor.

When passed, serverSideSearchParams will be used when window is undefined to access the search params. This is useful for generating content on the server, i.e. with Next.js:

import url from "url";

export const getServerSideProps: GetServerSideProps = ({ req }) => {
  const serverSideSearchParams = url.parse(req.url).query;

  return {
    props: { serverSideSearchParams },
  };
};

export default function Home({
  serverSideSearchParams,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  const counter = useSearchParam<number>("counter", {
    serverSideSearchParams,
  });

  // has the correct value for `counter` when rendered on the server
  return <div>counter: {counter}</div>;
}

Note that if no serverSideSearchParams option is passed and window is undefined, you may encounter hydration errors.

Additional Exports

Building your own useSearchParam

You can build useSearchParam yourself with buildUseSearchParam to implicitly pass sanitize, parse, and onError options to every instance of the created hook:

import { buildUseSearchParam } from "use-search-param";

// import this instance of `useSearchParam` in your components
export const useSearchParam = buildUseSearchParam({
  sanitize: (unsanitized) => yourSanitizer(unsanitized),
});

Imperative getSearchParam

For cases when you want to read from the search param outside a React component, two additional exports are provided: getSearchParam and buildGetSearchParam. Both have the same function signatures and behavior as their useSearchParam and buildUseSearchParam counterparts, with one exception: getSearchParam has no way to "react" to popstate, pushState, and replaceState events - unlike useSearchParam.

Adapting useSearchParam for your own router

By default, useSearchParam will read from window.location.search and monkey-patch the global history to properly react to pushState and replaceState events (see the wouter source code for more info on this topic). If you'd rather avoid the monkey-patching and have your existing router trigger any rerenders on route events, you can use getSearchParamFromSearchString to build your own hook:

// using wouter, for example:
import { useSearch } from "wouter";
import {
  getSearchParamFromSearchString,
  UseAdaptedSearchParamOptions,
} from "use-search-param/router-adapter";

export function useAdaptedSearchParam<TVal>(
  searchParamKey: string,
  options?: UseAdaptedSearchParamOptions<TVal> = {},
) {
  const searchString = useSearch();
  return getSearchParamFromSearchString<TVal>(searchParamKey, {
    searchString,
    ...options,
  });
}

Testing

The best approach to test uses of useSearchParam / getSearchParam is by mocking the window.location property directly in your tests:

Object.defineProperty(window, "location", {
  writable: true,
  value: { search: "?counter=1" },
});

If you mutate window.location directly, i.e.

window.location = { search: "?counter=1" };

You may receive an error that window.location is read-only.

Alternatively, you could mock useSearchParam/getSearchParam itself:

import * as useSearchParamWrapper from "use-search-param";

jest.spyOn(useSearchParamWrapper, "useSearchParam").mockReturnValue(1);
jest.spyOn(useSearchParamWrapper, "getSearchParam").mockReturnValue(1);

But this shadows the actual behavior of useSearchParam / getSearchParam and any of the options passed, and it may lead to unexpected behavior.