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-persist-state-hook

v1.0.2

Published

A drop-in replacement for React.useState that persists state to localStorage with expiry, cross-tab sync, and SSR (Next.js) support.

Readme

react-persist-state-hook

A drop-in replacement for React.useState that automatically persists state to localStorage — with built-in support for expiry, cross-tab sync, and full SSR / Next.js compatibility.

No extra setup. No context providers. No wrappers. Just swap useState with usePersistentState and your state survives page refreshes.


Features

  • localStorage sync — state is automatically saved and restored across page reloads
  • Expiry — optionally auto-clear state after a set number of minutes
  • Cross-tab sync — state updates in real time across all open browser tabs
  • SSR safe — works in Next.js and any server-side rendering environment without throwing
  • TypeScript first — full generic type support out of the box
  • Zero dependencies — only React as a peer dependency
  • Tiny — under 2kb minified and gzipped

Installation

npm install react-persist-state-hook
yarn add react-persist-state-hook
pnpm add react-persist-state-hook

Quick Start

import { usePersistentState } from "react-persist-state-hook";

function ThemeToggle() {
  const [theme, setTheme] = usePersistentState("theme", "light");

  return (
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
      Current theme: {theme}
    </button>
  );
}

That is all you need. The theme will persist across page refreshes automatically.


API

const [state, setState, clear] = usePersistentState(key, defaultValue, options);

Parameters

| Parameter | Type | Required | Description | | -------------- | ---------------- | -------- | ----------------------------------------- | | key | string | Yes | Unique localStorage key | | defaultValue | T | Yes | Initial value used when nothing is stored | | options | PersistOptions | No | Configuration — see below |

Returns

| Value | Type | Description | | ---------- | ----------------------------- | ------------------------------------------------------------- | | state | T | Current state value | | setState | Dispatch<SetStateAction<T>> | Same as React.useState setter — supports functional updates | | clear | () => void | Removes the value from localStorage and resets to default |

Options

| Option | Type | Default | Description | | ---------------- | ---------------------- | ---------------- | ------------------------------------------- | | expiryMinutes | number | undefined | Auto-clear state after this many minutes | | syncAcrossTabs | boolean | true | Sync state changes across open browser tabs | | serializer | (value: T) => string | JSON.stringify | Custom serializer | | deserializer | (value: string) => T | JSON.parse | Custom deserializer |


Examples

Complete single-app demo (all features in one file)

If you want one GitHub-ready example that shows everything together, use:

It demonstrates:

  • basic string persistence
  • typed object persistence using generics
  • expiry (expiryMinutes) with live value updates
  • cross-tab sync ON and OFF (syncAcrossTabs)
  • custom serializer and deserializer with Date
  • functional updater (setState(prev => ...))
  • manual clear via the clear function

Minimal usage inside your app:

import FullFeaturesDemo from "./examples/FullFeaturesDemo";

export default function App() {
  return <FullFeaturesDemo />;
}

If your example app is in a different repository, copy FullFeaturesDemo.tsx and change the import line to:

import { usePersistentState, type PersistOptions } from "react-persist-state-hook";

Basic usage — persists across page reloads

const [username, setUsername] = usePersistentState("username", "");

With expiry — auto-clears after 30 minutes

const [token, setToken, clearToken] = usePersistentState("auth-token", null, {
  expiryMinutes: 30,
});

// Log user out manually
function logout() {
  clearToken();
}

With cross-tab sync disabled

const [count, setCount] = usePersistentState("count", 0, {
  syncAcrossTabs: false,
});

Functional updater — works exactly like useState

const [count, setCount] = usePersistentState("count", 0);

// Both of these work
setCount(5);
setCount((prev) => prev + 1);

With objects and arrays

interface UserPreferences {
  theme: "light" | "dark";
  language: string;
  notifications: boolean;
}

const [prefs, setPrefs, clearPrefs] = usePersistentState<UserPreferences>(
  "user-prefs",
  { theme: "light", language: "en", notifications: true },
);

// Update a single field
setPrefs((prev) => ({ ...prev, theme: "dark" }));

With custom serializer (e.g. for Date objects)

const [lastVisit, setLastVisit] = usePersistentState<Date>(
  "last-visit",
  new Date(),
  {
    serializer: (date) => date.toISOString(),
    deserializer: (str) => new Date(str),
  },
);

SSR / Next.js — works without any extra setup

// This works in a Next.js page or app router component
// No "use client" guards needed around the hook itself
const [theme, setTheme] = usePersistentState("theme", "light");

The hook detects the server environment and safely returns the default value during SSR, then hydrates from localStorage on the client.


How cross-tab sync works

When syncAcrossTabs is enabled (the default), the hook listens to the browser's native storage event. This event fires automatically in all other tabs when localStorage is updated — so any state change in one tab is instantly reflected in every other open tab without any polling or websockets.

Tab A: setTheme("dark")  →  Tab B and Tab C automatically update to "dark"

TypeScript

The hook is fully generic and infers the type from your default value automatically.

// Type is inferred as string
const [name, setName] = usePersistentState("name", "");

// Type is inferred as number
const [count, setCount] = usePersistentState("count", 0);

// Explicit generic for complex types
const [user, setUser] = usePersistentState<User | null>("user", null);

PersistOptions type

type PersistOptions<T> = {
  expiryMinutes?: number;
  syncAcrossTabs?: boolean; // default: true
  serializer?: (value: T) => string;
  deserializer?: (value: string) => T;
};

Passing options with explicit type

type Session = { token: string; issuedAt: number };

const options: PersistOptions<Session | null> = {
  expiryMinutes: 30,
  syncAcrossTabs: true,
  serializer: (value) => JSON.stringify(value),
  deserializer: (value) => JSON.parse(value) as Session | null,
};

const [session, setSession, clearSession] = usePersistentState<Session | null>(
  "session",
  null,
  options,
);

Browser Support

Works in all modern browsers that support localStorage and the storage event.

  • Chrome 4+
  • Firefox 3.5+
  • Safari 4+
  • Edge 12+

Running Tests

npm install
npm test

For coverage report:

npm run test:coverage

Contributing

Pull requests are welcome. For major changes please open an issue first to discuss what you would like to change.

  1. Fork the repo
  2. Create your feature branch (git checkout -b feature/your-feature)
  3. Commit your changes (git commit -m "add your feature")
  4. Push to the branch (git push origin feature/your-feature)
  5. Open a pull request

License

MIT — see LICENSE for details.


Author

Debabrata Basak linkedin.com/in/coderboy061 | github.com/coderboy061/react-persist-state-hook