nusm axolotlnusm
Getting Started

Quick Start

Install nusm, create a store, choose its lifetime, and update it safely.

Install

npm install nusm

Start with an in-memory store

Persistence is optional. createNusmStore without an adapter creates an ordinary in-memory store:

src/stores/counter.ts
import { createNusmStore } from "nusm"

export const counterStore = createNusmStore({ count: 0 })

It uses the same state, setState, subscribe, React, and Devtools APIs as a persisted store. Its ready promise resolves immediately, hydration.overall is not_configured, and its state resets when the runtime reloads.

This lets an application standardize on nusm without forcing every store into browser storage.

Add persistence to the stores that need it

src/stores/counter.ts
import {
  createLocalStorageAdapter,
  createNusmStore,
} from "nusm"

export const counterStore = createNusmStore(
  { count: 0 },
  {
    adapter: createLocalStorageAdapter(),
    persist: { strategy: "entire" },
    storeId: "counter",
  },
)

storeId is required whenever an adapter is present. It gives the persistence unit a stable identity across reloads. With the default local storage prefix, this store uses nusm:counter:entire.

Persistence is configured per store. Mix in-memory, session storage, local storage, IndexedDB, and custom-adapter stores in one application. If you provide an adapter without persist, nusm defaults to entire-store persistence, but an explicit strategy makes the intended boundary clear.

Wait for hydration

Adapter reads are asynchronous from the store's point of view. Wait for ready before treating state as hydrated:

await counterStore.ready

console.log(counterStore.state)
console.log(counterStore.isReady) // true
console.log(counterStore.hydration.overall) // "hydrated"

If no value exists yet, nusm writes the initial state as the adapter baseline before ready resolves. A missing key is therefore a normal first-run hydration, not an error.

isReady and hydration are readonly snapshots. Read them again after lifecycle events; do not hold and mutate an older snapshot.

Update the store

Use the standard TanStack Store update API:

counterStore.setState((state) => ({
  count: state.count + 1,
}))

nusm observes the update and schedules persistence through the configured adapter. The local storage adapter batches rapid changes with a trailing 50 ms wait.

Subscribe outside React

const subscription = counterStore.subscribe(() => {
  console.log("count", counterStore.state.count)
})

// Later
subscription.unsubscribe()

TanStack Store versions may expose either an unsubscribe function or an object with unsubscribe; follow the shape returned by your installed version.

Use it in React

Install React integration peers if your application does not already include them, then import from the dedicated entry point:

src/Counter.tsx
import { useStore } from "nusm/react"
import { counterStore } from "./stores/counter"

export function Counter() {
  const count = useStore(counterStore, (state) => state.count)

  return (
    <button
      type="button"
      onClick={() =>
        counterStore.setState((state) => ({ count: state.count + 1 }))
      }
    >
      Count: {count}
    </button>
  )
}

For an application that must not render persisted UI early, resolve counterStore.ready during bootstrap or expose a small React-ready flag before mounting the hydrated view.

Handle storage failures

const store = createNusmStore(
  { count: 0 },
  {
    adapter: createLocalStorageAdapter(),
    onError(error) {
      console.error("counter persistence failed", error)
    },
    persist: { strategy: "entire" },
    storeId: "counter",
  },
)

try {
  await store.ready
} catch (error) {
  // Configuration-level or baseline hydration failures reject ready.
}

Per-unit read failures are reflected in hydration and reported to onError. Fatal hydration failures reject ready. Later write failures are also sent to onError.

Next steps

On this page