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

react-stateful-hooks

v0.1.3

Published

A small, well-typed collection of SSR-safe React hooks for browser state: persisted storage with cross-tab sync, debounced values, and media queries.

Readme

react-stateful-hooks

CI types license

A small, well-typed, SSR-safe collection of React hooks for working with browser state. Tree-shakeable, ships ESM + CJS + types, and its only runtime dependency is React's official use-sync-external-store shim (for React 17)

The problem: every project re-implements "persist this bit of state to localStorage" — and most versions break under SSR, crash on corrupted JSON, or silently drift out of sync between tabs. This library does it once, correctly

Hooks at a glance

| Hook | What it does | | --- | --- | | useLocalStorageState | Persisted state with cross-tab sync | | useSessionStorageState | Per-tab persisted state | | useDebouncedValue | Trailing-edge debounce of a value | | useMediaQuery | Reactive, SSR-safe CSS media query | | useNetworkState | Reactive, SSR-safe online/offline status | | useCopyToClipboard | Copy with auto-resetting "copied" feedback | | usePrefersColorScheme | Reactive 'light' \| 'dark' preference | | usePrefersReducedMotion | Reactive reduced-motion preference |

Install

npm install react-stateful-hooks

react >= 17 is a peer dependency

useLocalStorageState

A drop-in useState that persists to localStorage and stays in sync across browser tabs

import { useLocalStorageState } from 'react-stateful-hooks';

function ThemeToggle() {
  const [theme, setTheme, resetTheme] = useLocalStorageState('theme', 'light');

  return (
    <>
      <button onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}>
        Theme: {theme}
      </button>

      <button onClick={resetTheme}>Reset</button>
    </>
  );
}

Signature

const [value, setValue, removeValue] = useLocalStorageState<T>(
  key: string,
  defaultValue: T,
  options?: {
    serializer?: { parse(raw: string): T; stringify(value: T): string };
    syncTabs?: boolean; // default: true
  },
);

| Return | Description | | --- | --- | | value | Current value (typed as T) | | setValue | Accepts a value or an updater (prev) => next, like useState | | removeValue | Clears the key from storage and resets state to defaultValue |

Behaviour worth knowing

  • SSR-safe — built on useSyncExternalStore, so it returns defaultValue on the server and hydrates without a mismatch, then reads storage on the client
  • Resilient — corrupted JSON or a getItem/setItem failure (quota, private mode) falls back to the default and keeps the in-memory value instead of throwing
  • Cross-tab sync — listens to the storage event and updates state when another tab writes the same key. Disable with { syncTabs: false }. Hooks in the same tab always stay in sync, regardless of this flag
  • Custom serialization — pass a serializer to support Date, Map, BigInt, or a compact wire format
const [since, setSince] = useLocalStorageState('since', new Date(), {
  serializer: {
    parse: (raw) => new Date(JSON.parse(raw)),
    stringify: (value) => JSON.stringify(value.getTime()),
  },
});

useSessionStorageState

Same API and guarantees as useLocalStorageState, but backed by sessionStorage (state lives until the tab closes). Ideal for wizard steps, scroll positions, or any throwaway-per-session state

const [step, setStep] = useSessionStorageState('wizard:step', 0);

useDebouncedValue

Returns a debounced copy of a value that only updates after the delay passes without further changes — rapid updates collapse into a single trailing update

const [query, setQuery] = useState('');
const debouncedQuery = useDebouncedValue(query, 300);

useEffect(() => {
  search(debouncedQuery);
}, [debouncedQuery]);

useMediaQuery

Tracks whether a CSS media query matches and re-renders on change. SSR-safe — returns defaultState (default false) on the server

const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
const isWide = useMediaQuery('(min-width: 1024px)');

useNetworkState

Tracks the browser's online/offline status. SSR-safe — returns { online: defaultOnline } (default true) on the server. since is the time of the last status change, or undefined until the first transition

const { online, since } = useNetworkState();

if (!online) return <Banner>You are offline.</Banner>;
const { online, since } = useNetworkState(defaultOnline?: boolean); // default: true

useCopyToClipboard

Returns a copy function plus the state of the last copy attempt. Uses the async Clipboard API (requires a secure context); when it's unavailable, copy resolves false and records an error instead of throwing. copied flips back to false after resetDelay ms so "Copied!" feedback needs no manual timer

const [copy, { copied }] = useCopyToClipboard();

<button onClick={() => copy(url)}>
    {copied ? 'Copied!' : 'Copy link'}
</button>
const [copy, { value, error, copied }] = useCopyToClipboard(options?: {
  resetDelay?: number; // ms until `copied` resets; 0 = never. default: 2000
});

usePrefersColorScheme

Tracks the user's preferred color scheme via (prefers-color-scheme: dark) SSR-safe — returns defaultScheme (default 'light') on the server

const scheme = usePrefersColorScheme(); // 'light' | 'dark'

return <div data-theme={scheme} />;
const scheme = usePrefersColorScheme(defaultScheme?: 'light' | 'dark'); // default: 'light'

usePrefersReducedMotion

Tracks whether the user has requested reduced motion via (prefers-reduced-motion: reduce). SSR-safe — returns defaultValue (default false) on the server

const reduceMotion = usePrefersReducedMotion();

<motion.div animate={reduceMotion ? undefined : { x: 100 }} />;

Development

npm install
npm test          # Vitest + Testing Library (jsdom)
npm run lint
npm run typecheck
npm run build     # ESM + CJS + .d.ts via Vite library mode

License

MIT © Evgenii Pokalyuk