use-stored-state
v1.4.1
Published
A handy React hook for synchronizing state with localStorage, sessionStorage, and query parameters.
Maintainers
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-statePeer 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,localStorageKeyis required. sessionStorageKeyandlocalStorageKeycannot be used together.- Use
validValuesorvalidate(not both). - If you pass
parse, you must also passserialize(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:
- Query param (
queryKey) - Session storage (
sessionStorageKey) or local storage (localStorageKey) 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.
sessionStorageKeyandlocalStorageKeyare mutually exclusive.- Any omitted store key is not read or written.
Query param lifecycle
- If
queryKeyis 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 validationvalidate: custom predicate validationdefaultValuemust pass validation, otherwise the hook throws
Parsing and serialization
By default, primitive values are handled as:
boolean:"true"/"false"number:Number(rawValue)(rejectsNaN)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 testnpm run lintnpm run prettiernpm run mutatenpm run type-checknpm run knipnpm run markdownlintnpm run check
Mutation Testing Workflow
Mutation testing is a required quality gate for this project.
Acceptance criteria:
- Mutation score must be
100% 0surviving mutants0timed out mutants
Recommended workflow:
- Run mutation tests while developing:
npm run mutate
- Add or improve tests until all mutants are killed.
- Re-run mutation tests to verify.
- 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.
