Skip to content

MaximSrour/useStoredState

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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, { reset }] = 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>
      <button onClick={reset} type="button">
        Reset
      </button>
    </>
  );
}

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, { reset }];

You can also destructure only [state, setState] if you do not need reset behavior.

setState only applies valid values. reset() restores defaultValue. When state is not already at defaultValue, it follows the normal state update and synchronization path. When state is already at defaultValue, reset() still re-synchronizes the configured stores so it can repair drifted query or storage 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.

About

No description, website, or topics provided.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors