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

@oleksandr_leskiv/use-shared-state

v1.0.2

Published

Fully-typed shared state for React with `useState` semantics.

Readme

use-shared-state

Fully-typed shared state for React with useState semantics.


version MIT License TypeScript PRs Welcome

What is this?

use-shared-state is a small, React-only hook library that lets you share state between unrelated components with the same API and mental model as React’s useState.

It supports both in-memory shared state and optional, per-key persistence to browser storages such as localStorage and sessionStorage, with support for custom storage backends and custom serialization.

State is shared by globally typed keys, ensuring that:

  • every key has exactly one type across the entire app
  • all consumers stay automatically synchronized
  • no reducers, actions, or selectors are needed

Quick example

Define a shared key once:

declare module 'use-shared-state' {
  export interface SharedKeys {
    apiToken: string | null
  }
}

Update shared state in one component:

import { useSharedState } from 'use-shared-state'

function LoginScreen() {
  const [, setApiToken] = useSharedState('apiToken')(null)

  return (
    <button onClick={() => setApiToken('secret-token')}>
      Login
    </button>
  )
}

Consume the same state somewhere else:

function UserProfile() {
  const [apiToken] = useSharedState('apiToken')()

  return apiToken
    ? <span>Authenticated</span>
    : <span>Not authenticated</span>
}

Table of Contents

What it gives you

  • useState-compatible API (useSharedState(key) returns the same tuple)
  • Fully typed shared keys via TypeScript module augmentation
  • Automatic synchronization across components using the same key
  • Optional scoping via Provider
  • Optional per-key persistence to localStorage, sessionStorage, or custom storage backends
  • Dispatch-only hook to avoid rerenders when you only need to update state

Installation

npm install use-shared-state
pnpm add use-shared-state
yarn add use-shared-state

Usage

import { useSharedState } from 'use-shared-state'

useSharedState(key) returns a useState-like hook. It accepts:

  • a default value: useSharedState('key')(defaultValue)
  • an initializer function: useSharedState('key')(() => defaultValue)
  • nothing: useSharedState('key')() (value will be T | undefined)

Initialization and synchronization rules

For each key, the first component to render decides the initial value:

  1. If persistence is enabled and a stored value exists, it is restored.
  2. Otherwise, if a default value / initializer was provided, it is used.
  3. Otherwise, the value remains undefined.

If the first usage results in undefined, a later usage may still provide a default and initialize the key. Once initialized, the value is shared and kept in sync for all consumers.

Provider

By default, you do not need a Provider at all.

If you don’t render Provider, use-shared-state uses a single global store for the entire application.

Use Provider only when you want to isolate shared state for a subtree.

import { Provider } from 'use-shared-state'

function App() {
  return (
    <Provider>
      <Feature />
    </Provider>
  )
}

Nested providers create independent stores.

Persistence

Persistence is configured per key via the Provider's storeConfig prop.

If you don’t need persistence, you don’t need to configure anything.

<Provider
  storeConfig={{
    persist: {
      apiToken: true,               // localStorage
      language: sessionStorage,     // custom storage
      complex: {
        storage: true,
        customEncoding: { encode, decode },
      },
    },
  }}
>
  <Root />
</Provider>

Rules:

  • truelocalStorage
  • storage-like object → that storage
  • false or missing key → persistence disabled
  • values are JSON-encoded by default
  • customEncoding allows custom serialization
  • setting a value to null removes the persisted entry

Dispatch-only hook

Use useSharedStateDispatch if you only need to update state and want to avoid rerenders:

import { useSharedStateDispatch } from 'use-shared-state'

function LogoutButton() {
  const setApiToken = useSharedStateDispatch('apiToken')

  return <button onClick={() => setApiToken(null)}>Logout</button>
}

TypeScript experience

This library is designed to be TypeScript-first.

  • Keys must exist in SharedKeys
  • Each key has exactly one type across the entire app
  • Autocomplete works for keys and values
  • Using an unknown key or wrong type is a compile-time error

This design intentionally prevents subtle runtime bugs caused by inconsistent shared state shapes.

Update semantics

setState behaves exactly like React’s useState setter:

  • accepts a value or an updater function
  • no automatic merging
  • updates always notify all subscribers, even if the value is unchanged

Limitations

  • React-only
  • Uses browser storage APIs for persistence
  • Not SSR-safe by default (client-only usage recommended)

Motivation and comparison

use-shared-state is designed for shared client-side state with the same ergonomics as useState, plus optional persistence.

At a glance

| Tool | What it’s best for | Key trade-off | |---|---|---| | useState | Local component state | Not shareable | | useContext | Global values / dependencies | Centralized ownership | | useReducer | Complex state transitions | Boilerplate (actions/reducers) | | use-between | Sharing state via custom hooks | Requires a dedicated hook per entity | | Zustand / Jotai | Full app state management | Extra abstraction | | use-shared-state | Shared state, writable from anywhere | Key-based, not hook-based |

One-sentence summary

  • useContext → global values, usually owned by a provider
  • useReducer → shared state via actions and reducers
  • use-between → share state by creating and reusing a hook per entity
  • Zustand / Jotai → external stores for broader state management
  • use-shared-stateuseState, but shared by typed keys, writable from anywhere, optionally persistent

FAQ

Why are generics not supported?

To ensure the same key cannot be used with different types in different parts of the application.

Why is my value undefined?

Because no persisted value exists and no default value has been provided yet.

Why didn’t my default value apply?

Once a key is initialized, later defaults are ignored.

License

MIT