Skip to content

persist

Persists and hydrates store state using a storage backend. Defaults to localStorage. Any backend implementing getItem / setItem / removeItem is accepted — including async ones.

Basic usage

ts
import { withPlugins } from '@kin-store/core';
import { persist } from '@kin-store/plugins';

const store = withPlugins({ count: 0 })
  .use({
    reducers: {
      increment: (state, n: number) => ({ count: state.count + n }),
    },
  })
  .use('persist', persist({ key: 'my-counter' }));

store.dispatch.increment(1);
// State is automatically saved to localStorage['my-counter'].
// On the next page load, it is restored automatically.

Persist a slice

ts
.use('persist', persist({
  key: 'app',
  selector: (s) => ({ token: s.token }),
}))

Schema versioning

ts
.use('persist', persist({
  key: 'items',
  version: 1,
  migrate(stored: MyState, version: number): MyState {
    if (version === 0) return { items: stored.todos ?? [] };
    return stored;
  },
}))

Custom async storage

ts
const asyncStorage: PersistStorage = {
  async getItem(key: string): Promise<string | null> {
    return await myDB.get(key);
  },
  async setItem(key: string, value: string): Promise<void> {
    await myDB.set(key, value);
  },
  async removeItem(key: string): Promise<void> {
    await myDB.delete(key);
  },
};

.use('persist', persist({ key: 'data', storage: asyncStorage }))

SSR — skip auto-hydration

When rendering server-side, skip the automatic hydration and trigger it manually on the client:

ts
.use('persist', persist({ key: 'user', skipHydration: true }))

// On the client.
await store.persist.hydrate();

Plugin methods

Once registered under a namespace (e.g. 'persist'), the plugin exposes:

MethodDescription
store.persist.hydrate()Triggers a hydration; returns the in-progress hydration if one exists
store.persist.hasHydrated()true if at least one hydration has completed
store.persist.hydrationComplete()Promise that resolves after the current or next hydration
store.persist.clear()Removes the persisted value from storage
store.persist.onHydrationStart(cb)Called at the start of each hydration
store.persist.onHydrationComplete(cb)Called when a hydration completes

Composing with history

After async hydration completes, call history.rebase() so undo doesn't step back to the pre-hydration state:

ts
const store = withPlugins({ items: [] as string[] })
  .use('persist', persist({ key: 'items' }))
  .use('history', history())
  .use({ reducers: { add: (s, t: string) => ({ items: [...s.items, t] }) } });

await store.persist.hydrationComplete();
store.history.rebase();

Options

OptionTypeDefaultDescription
keystringrequiredStorage key
storagePersistStoragelocalStorageStorage backend
selector(state) => partialfull stateSlice to persist
versionnumber0Schema version for migrations
migrate(stored, version) => stateMigration function
skipHydrationbooleanfalseSkip auto-hydration on registration

Released under the MIT License.