Skip to content

Persist store state in localStorage

Cami provides one browser persistence adapter: versioned localStorage. Use it for small, non-secret preferences and drafts that should survive a reload.

Do not persist authentication tokens or sensitive user data. Browser storage is readable by scripts running on the same origin.

Create and hydrate a store

import { createLocalStorage, persistToLocalStorageThunk, store } from 'cami'

const PreferencesStore = store({
  name: 'preferences',
  state: { theme: 'system', density: 'comfortable' },
})

PreferencesStore.defineAction('hydrate', ({ state, payload }) => {
  Object.assign(state, payload)
})

const storage = createLocalStorage({ name: 'preferences', version: 1 })
const saved = await storage.getState()
if (saved) PreferencesStore.dispatch('hydrate', saved)

PreferencesStore.afterHook(persistToLocalStorageThunk(storage))
import { createLocalStorage, persistToLocalStorageThunk, store } from 'cami'

type Theme = 'light' | 'dark' | 'system'
type Density = 'comfortable' | 'compact'

interface PreferencesState {
  theme: Theme
  density: Density
}

const PreferencesStore = store<PreferencesState>({
  name: 'preferences',
  state: { theme: 'system', density: 'comfortable' },
})

PreferencesStore.defineAction('hydrate', ({ state, payload }) => {
  Object.assign(state, payload as Partial<PreferencesState>)
})

const storage = createLocalStorage<PreferencesState>({ name: 'preferences', version: 1 })
const saved = await storage.getState()
if (saved) PreferencesStore.dispatch('hydrate', saved)

PreferencesStore.afterHook(persistToLocalStorageThunk(storage))

Hydrate before components depend on the store. Then attach the persistence hook. This avoids a startup rewrite.

Version behavior

createLocalStorage({ name, version }) records the version beside the stored value.

  • The first version creates a storage record.
  • The same version reuses the record.
  • A higher version clears the old value.

Increment the version when the stored shape is incompatible. A version change clears the old value. For migration, read and transform the old record before creating the new adapter.

Keep persisted state small

The built-in hook writes the complete store after a transition. Keep persisted preferences in a small store. Do not persist a large application store.

For large collections, offline databases, or cross-tab coordination, use an application-owned adapter. Those concerns need domain-specific indexes, migrations, conflict behavior, and failure handling, so they are outside Cami's core API.

API

Export Purpose
createLocalStorage({ name, version }) Read and write one versioned JSON value
persistToLocalStorageThunk(adapter) Create an afterHook that writes changed store state