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-stored-state

v1.4.1

Published

A handy React hook for synchronizing state with localStorage, sessionStorage, and query parameters.

Readme

use-stored-state

use-stored-state is a React hook that keeps state synchronized with:

  • URL query params (optional)
  • Session storage or local storage (optional, mutually exclusive)

It gives you a useState-like API with persistence, hydration priority, and validation built in.

Install

npm install use-stored-state

Peer dependency:

  • react >= 18

Quick Start

import { useStoredState } from "use-stored-state";

function Example() {
  const [pageSize, setPageSize] = useStoredState({
    defaultValue: 25,
    queryKey: "pageSize",
    sessionStorageKey: "usersPageSize",
    validValues: [10, 25, 50, 100] as const,
  });

  return (
    <>
      <label htmlFor="page-size">Users per page</label>
      <select
        id="page-size"
        value={pageSize}
        onChange={(event) => setPageSize(Number(event.target.value))}
      >
        <option value={10}>10</option>
        <option value={25}>25</option>
        <option value={50}>50</option>
        <option value={100}>100</option>
      </select>
    </>
  );
}

API

useStoredState(options)

type UseStoredStateOptions<State> = {
  defaultValue: State;

  // Provide at least one key:
  queryKey?: string;
  sessionStorageKey?: string;
  localStorageKey?: string;

  // Validation (choose one or neither):
  validValues?: readonly State[];
  validate?: (value: State) => boolean;

  // Parsing/serialization (provide both or neither):
  parse?: (rawValue: string) => State | null;
  serialize?: (value: State) => string;
};

Rules:

  • At least one of queryKey, sessionStorageKey, localStorageKey is required.
  • sessionStorageKey and localStorageKey cannot be used together.
  • Use validValues or validate (not both).
  • If you pass parse, you must also pass serialize (and vice versa).
  • Invalid option combinations throw at runtime (including JavaScript-only usage).

Returns:

[state, setState];

Where setState only applies valid values.

Behavior

Hydration order

Initial state is resolved in this order:

  1. Query param (queryKey)
  2. Session storage (sessionStorageKey) or local storage (localStorageKey)
  3. defaultValue

Invalid hydrated values are ignored when validValues or validate is used.

Synchronization

  • On mount and on each valid state update, the hook syncs current state to all configured stores (query + at most one storage source).
  • At least one key is required.
  • sessionStorageKey and localStorageKey are mutually exclusive.
  • Any omitted store key is not read or written.

Query param lifecycle

  • If queryKey is set, the query param is populated on mount.
  • On unmount, that query param is removed.
  • If multiple mounted hooks share the same queryKey, the param is only removed after the last one unmounts.

Validation

  • validValues: allow-list validation
  • validate: custom predicate validation
  • defaultValue must pass validation, otherwise the hook throws

Parsing and serialization

By default, primitive values are handled as:

  • boolean: "true" / "false"
  • number: Number(rawValue) (rejects NaN)
  • string: unchanged

For custom state shapes, provide parse and serialize.

useKeyStore

useKeyStore is the low-level hook used by useStoredState.

import { useKeyStore } from "use-stored-state";

It syncs a single source (query, sessionStorage, or localStorage) and returns [state, setState].

Development

Useful commands:

  • npm run test
  • npm run lint
  • npm run prettier
  • npm run mutate
  • npm run type-check
  • npm run knip
  • npm run markdownlint
  • npm run check

Mutation Testing Workflow

Mutation testing is a required quality gate for this project.

Acceptance criteria:

  • Mutation score must be 100%
  • 0 surviving mutants
  • 0 timed out mutants

Recommended workflow:

  1. Run mutation tests while developing:
    • npm run mutate
  2. Add or improve tests until all mutants are killed.
  3. Re-run mutation tests to verify.
  4. Run the full mutation suite before opening a PR:
    • npm run mutate

If a mutant is equivalent and cannot be killed by a meaningful test:

  • Prefer rewriting code to make intent explicit and testable.
  • If still equivalent, add a targeted Stryker disable comment with a clear reason and keep the suppression as narrow as possible.