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

@technicalshree/use-localstorage

v0.2.2

Published

Feature-rich React hook that keeps state synchronised with localStorage across tabs and sessions.

Readme

useLocalStorage

A tiny React hook that keeps component state in sync with localStorage, so data persists across reloads and even across browser tabs. The API mirrors useState, making it a drop-in replacement when persistence is required.

When to Use

  • Persist user preferences such as themes, locale, or layout density between visits.
  • Maintain form drafts or onboarding progress without building a backend service.
  • Cache lightweight API responses for faster repeat renders in the same browser session.
  • Mirror authentication metadata that should survive refreshes but can remain client-side.

Installation

npm install @technicalshree/use-localstorage

Quick Start

import { useLocalStorage } from '@technicalshree/use-localstorage';

function Preferences() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

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

Features

  • Reads the current value from localStorage, falling back to the provided initial value.
  • Persists updates and synchronises across tabs using the browser storage event.
  • Accepts custom serializer/deserializer pairs for encryption, schema validation, or richer data types.
  • Supports TTL eviction with onExpire callbacks and cross-tab expiry broadcasts.
  • Handles versioned payloads and migrations so older data can be upgraded on read.
  • Normalises legacy string booleans (e.g. 'true', 'false') back into real booleans when the initial value is boolean.
  • Offers useSyncedStorage to target localStorage, sessionStorage, or custom adapters (including server-safe memory storage).
  • Includes useObjectLocalStorage helpers for ergonomic partial updates and resets of nested state.
  • Safe to import in SSR/SSG environments—the hook checks for the presence of window before touching storage.

API

const [value, setValue] = useLocalStorage<T>(
  key: string,
  initialValue: T | (() => T),
  options?: UseLocalStorageOptions<T>
);

| Parameter | Type | Description | | --- | --- | --- | | key | string | Storage key used in localStorage. | | initialValue | T \| (() => T) | Value to use when nothing is stored. Lazy functions are invoked only when needed. | | options | UseLocalStorageOptions<T> | Optional behaviour overrides detailed below. |

| Returns | Type | Description | | --- | --- | --- | | value | T | Current value pulled from storage or the initial fallback. | | setValue | (next: T \| ((previous: T) => T) \| undefined) => void | Persists the next value. Passing undefined removes the key before falling back to the initial value. |

The hook returns a tuple identical to useState:

  • value: The current value (from storage or the initial value).
  • setValue(next): Persists next to state and localStorage. Accepts either a value or an updater function.

Calling setValue(undefined) removes the key from storage and resets the hook back to the initial value.

Next.js & SSR: The hook only touches localStorage when window exists, so it is safe to import in server-rendered bundles. Use it inside client components to avoid hydration warnings.

Options

| Option | Type | Description | | --- | --- | --- | | serializer | (value: T) => string | Transforms values before writing. Pair with deserializer for encryption or schema-aware persistence. Defaults to JSON.stringify. | | deserializer | (raw: string) => T | Re-hydrates values read from storage. Defaults to JSON.parse. | | ttl | number | Milliseconds until the entry expires. Expiry removes the key, resets state to the initial value, and emits callbacks/events. | | onExpire | ({ key }: { key: string }) => void | Invoked when a value expires due to TTL. | | onError | (error, context) => void | Notifies about serialization, deserialization, or storage errors. | | onExternalChange | ({ key, value, event }) => void | Fired when another tab (or an expiry) updates the stored value. The value is undefined if the entry was removed. | | version | number | Version tag stored alongside the value. Use with migrate to upgrade older payloads. | | migrate | (value: T, storedVersion?: number) => T | Upgrades persisted data when the stored version differs from the current one. |

Need more than localStorage? Use useSyncedStorage for a storage-agnostic API or useObjectLocalStorage for ergonomic nested updates (see below).

Examples

Persisting complex objects

type Profile = { id: string; darkMode: boolean };

const defaultProfile: Profile = { id: 'guest', darkMode: false };

export function ProfileSettings() {
  const [profile, setProfile] = useLocalStorage<Profile>('profile', defaultProfile);

  return (
    <label>
      <input
        type="checkbox"
        checked={profile.darkMode}
        onChange={event =>
          setProfile(previous => ({ ...previous, darkMode: event.target.checked }))
        }
      />
      Enable dark mode
    </label>
  );
}

Boolean feature flags

export function ApprovalToggle() {
  const [isApproved, setIsApproved] = useLocalStorage('is_approved', true);

  return (
    <button onClick={() => setIsApproved(previous => !previous)}>
      {isApproved ? 'Approved' : 'Pending'}
    </button>
  );
}

If older deployments stored the flag as a raw string ("true"/"false"), the hook now converts those legacy values into real booleans on the first read and rewrites the stored value behind the scenes.

Reacting to cross-tab updates

export function ActiveSession() {
  const [session, setSession] = useLocalStorage('session', { status: 'guest' });

  useEffect(() => {
    const keepAlive = setInterval(() => {
      setSession(previous => ({ ...previous, refreshedAt: Date.now() }));
    }, 60_000);
    return () => clearInterval(keepAlive);
  }, [setSession]);

  return <span>Signed in as {session.status}</span>;
}

Time-to-live with expiry callbacks

function EphemeralNotice() {
  const [dismissed, setDismissed] = useLocalStorage('notice', false, {
    ttl: 60 * 60 * 1000,
    onExpire: ({ key }) => console.info(`Entry ${key} expired`)
  });

  if (dismissed) {
    return null;
  }

  return (
    <button onClick={() => setDismissed(true)}>
      Hide this notice for one hour
    </button>
  );
}

Custom storage via useSyncedStorage

import { useSyncedStorage } from '@technicalshree/use-localstorage';

const sessionAdapter = {
  getItem: (key: string) => sessionStorage.getItem(key),
  setItem: (key: string, value: string) => sessionStorage.setItem(key, value),
  removeItem: (key: string) => sessionStorage.removeItem(key)
};

export function SessionToken() {
  const [token, setToken] = useSyncedStorage('session-token', null, {
    storage: sessionAdapter,
    ttl: 15 * 60 * 1000
  });

  return (
    <button onClick={() => setToken(Math.random().toString(36).slice(2))}>
      Rotate session token (expires in 15 minutes)
    </button>
  );
}

Partial updates with useObjectLocalStorage

import { useObjectLocalStorage } from '@technicalshree/use-localstorage';

export function PreferencesPanel() {
  const [preferences, , helpers] = useObjectLocalStorage('preferences', {
    theme: 'light',
    density: 'comfortable'
  });

  return (
    <div>
      <button onClick={() => helpers.setPartial({ theme: 'dark' })}>
        Enable dark theme
      </button>
      <button onClick={() => helpers.reset()}>Reset defaults</button>
      <pre>{JSON.stringify(preferences, null, 2)}</pre>
    </div>
  );
}

Development

See DEVELOPMENT.md for detailed setup, testing, and publishing instructions.

Project Structure

use-localstorage/
├── src/index.ts        # hook implementation and exports
├── tests/index.test.ts # Vitest coverage for hook behaviour
├── tsconfig.json       # TypeScript configuration
├── tsup.config.ts      # Bundler configuration
└── README.md

License

MIT © Krushna Raut