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

urlstate-js

v0.3.0

Published

A tiny, typed React hook for URL query state.

Readme

urlstate-js

Minified Size Minified and Gzipped Size

npm version npm downloads Node.js License Tests

A very tiny, typed React library for storing state in URL query parameters.

URL state changing while typing, paginating and resetting

const [page, setPage] = useUrlState("page", 1);

setPage(2);
// ?page=2

The URL is the source of truth. Existing query parameters, the pathname and the hash are preserved.

Features

  • One hook for single or grouped query state.
  • Types inferred from defaults.
  • Typed parsing for strings, string arrays, finite numbers, booleans and literal values.
  • Browser Back and Forward synchronization.
  • replaceState by default and optional pushState.
  • Server-safe parsing for SSR.
  • No runtime dependency other than the React peer dependency.
  • About 1.3 kB gzip for the client and 0.6 kB gzip for the server parser.

Installation

npm install urlstate-js

Requirements:

  • React 18 or newer.
  • TypeScript 5 or newer for literal inference.

Quick start

"use client";

import { useUrlState } from "urlstate-js";

const Page = () => {
    const [search, setSearch] = useUrlState("search", "");

    return (
        <div>
            <input
                value={search}
                placeholder="Search..."
                onChange={(event) => setSearch(event.target.value)}
            />

            <button type="button" onClick={() => setSearch(null)}>
                Clear
            </button>
        </div>
    );
};

Opening /?search=react returns "react". Setting null, an empty default, or the configured default removes the query from the URL.

See simple.tsx for a complete example.

Multiple queries

Pass an object to read and update related queries together:

const [filters, setFilters] = useUrlState({
    archived: false,
    page: 1,
    search: "",
});

setFilters({ search: "react", page: 2 });

setFilters((previous) => ({
    page: previous.page + 1,
}));

Updates are atomic and only affect the configured keys. Other query parameters remain untouched.

See advanced.tsx for grouped state, literal values, literal arrays, functional updates, history and reset.

Supported values

Strings

const [search, setSearch] = useUrlState("search", "");

A string default accepts any string.

String arrays

Use an array default to store several string values in one query:

const [tags, setTags] = useUrlState("tags", []);

setTags(["react", "typescript"]);
// ?tags=react,typescript

String arrays use one comma-delimited query value. The comma is reserved as the separator and array items must not contain commas. This constraint is not validated by the library; the application is responsible for its values.

The comma remains readable in the URL. Setting the configured array default removes the query.

Numbers

const [page, setPage] = useUrlState("page", 1);

Only finite numbers are accepted. An invalid value such as ?page=invalid returns the default.

Booleans

const [archived, setArchived] = useUrlState("archived", false);

The URL accepts true, false, 1 and 0. Values written by the hook use true and false.

Literal strings

Use { default, values } when a query only accepts a fixed set:

const [theme, setTheme] = useUrlState("theme", {
    default: "light",
    values: ["light", "dark"],
});

setTheme("dark");
// setTheme("custom"); // TypeScript error

The inferred type is "light" | "dark". Missing or invalid values return "light". Reading an invalid URL does not mutate it; the invalid value is only ignored by the state.

The same configuration works in a group:

const [settings, setSettings] = useUrlState({
    page: 1,
    theme: {
        default: "light",
        values: ["light", "dark"],
    },
});

Literal string arrays

Use the same { default, values } configuration with an array default to only accept arrays made from a fixed set of strings:

const [addons, setAddons] = useUrlState("addons", {
    default: ["backup"],
    values: ["backup", "monitoring"],
});

setAddons(["backup", "monitoring"]);
// ?addons=backup,monitoring
// setAddons(["custom"]); // TypeScript error

The inferred type is ("backup" | "monitoring")[]. Every default item must be included in values. If any item read from the URL is not allowed, the whole array returns to the configured default. An empty array remains valid and is stored as ?addons= when the configured default is not empty.

Use a plain array default instead when any string should be accepted:

const [addons, setAddons] = useUrlState("addons", ["custom", "backup"]);

History

Updates use history.replaceState by default. This avoids adding a history entry for every input change:

setSearch("react");

Use push when the Back button should return to the previous value:

setPage(2, { history: "push" });
setFilters({ page: 2 }, { history: "push" });

Reset queries

Setting one state to null removes its query:

setSearch(null);

Use resetUrlState outside a setter or to reset several keys:

import { resetUrlState } from "urlstate-js";

resetUrlState("search");
resetUrlState(["search", "page"]);
resetUrlState(["search", "page"], { history: "push" });

Only the selected keys are removed. Mounted hooks update immediately and return to their configured defaults.

Server rendering

Use one plain object as the contract shared by the server and client:

// product-query.ts

export const productQuery = {
    archived: false,
    page: 1,
    search: "",
    tags: [] as string[],
    theme: {
        default: "light",
        values: ["light", "dark"],
    },
} as const;

Parse searchParams before fetching data in a Next.js Server Component:

// page.tsx

import { parseUrlState } from "urlstate-js/server";

import { Products } from "./products";
import { productQuery } from "./product-query";

const Page = async ({ searchParams }) => {
    const query = parseUrlState(await searchParams, productQuery);
    const products = await getProducts(query);

    return <Products initialQuery={query} products={products} />;
};

export default Page;

Pass the parsed state through props so the client starts with the same values:

// products.tsx

"use client";

import { useUrlState } from "urlstate-js";

import { productQuery } from "./product-query";

const Products = ({ initialQuery, products }) => {
    const [query, setQuery] = useUrlState(productQuery, {
        initial: initialQuery,
    });

    // ...
};

parseUrlState accepts a query string, URL, URLSearchParams, or a Next.js searchParams record. Client updates do not automatically rerun server fetches; that requires a framework navigation or refresh.

API

useUrlState

useUrlState(key, defaultValue, options?);
useUrlState(defaults, options?);

options.initial supplies the values used during server rendering and hydration.

The returned setter accepts a value, a partial object, or a functional update. Its optional { history: "push" | "replace" } argument controls browser history.

resetUrlState

resetUrlState(key, options?);
resetUrlState(keys, options?);

Removes one or more selected queries and notifies mounted hooks.

parseUrlState

import { parseUrlState } from "urlstate-js/server";

const state = parseUrlState(input, defaults);

Parses and validates URL input without importing React or browser code.

Development

npm run check

The check builds the package, lints, typechecks, runs the tests and enforces the client and server gzip budgets.