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
hydrate()Triggers a hydration; returns the in-progress hydration if one exists
hasHydrated()Returns true if at least one hydration has completed
hydrationComplete()Returns a promise that resolves after the current or next hydration
clear()Removes the persisted value from storage
onHydrationStart(cb)Registers a callback that is called at the start of each hydration
onHydrationComplete(cb)Registers a callback that is 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) => stateundefinedMigration function
skipHydrationbooleanfalseSkip auto-hydration on registration

Released under the MIT License