ilokesto

Persist

Sync state with localStorage, sessionStorage, or cookies.

Persist

The persist middleware automatically saves and restores store state across browser sessions. It provides a bridge between your in-memory store and long-term storage targets.

Usage

You can use persist either directly on a store object or as a curried middleware factory during store creation.

import { persist } from '@ilokesto/state/middleware';

const store = persist({ local: 'counter-storage' })({ count: 0 });

Storage Configuration

The middleware supports three primary storage targets through the configuration object:

  • local: (string) The key name for localStorage. Use this for data that should survive browser restarts.
  • session: (string) The key name for sessionStorage. Use this for data that should be cleared when the tab is closed.
  • cookie: (string) The key name for cookie storage. This is useful for sharing small pieces of state with the server.

The Rehydration Flow

When a store is initialized with persist, it follows a specific setup sequence:

  1. Initial Setup: The store is created with the provided initial state.
  2. Synchronous Read: The middleware immediately attempts to read the persisted state from the configured storage.
  3. Replacement: If valid data is found, that rehydrated state becomes the store's current state.
  4. Referential-Change Guard: The store reference is updated only if the persisted state differs from the initial state.

Storage Shape and Versioning

Data is saved in a wrapper object:

{
  "state": { ... },
  "version": 2
}
  • Version derivation: In the current model, the version is derived from the length of your migrate chain. If you have 3 migration functions, the version is 3.
  • Write-Through Behavior: After every store commit, the middleware writes the new state to storage.
  • Write Cache: To avoid redundant I/O, the middleware caches the encoded persisted payload and skips writes when the next encoded value is identical.

Migrations

The migrate option accepts an array of functions to transform old state shapes into the current one.

  • Target limitations: Currently, migrations are only supported for local and cookie targets. session storage does not support the migration flow.
  • Incremental updates: Each function in the array represents one version step. If the stored version is 1 and the current chain length is 3, functions [1] and [2] will be applied sequentially.

When using the cookie target, keep in mind:

  • Size: Cookies are typically limited to 4KB. Large state trees will fail to persist.
  • Serialization: Data is JSON-serialized. Ensure your state does not contain circular references or non-serializable objects (like Functions or Classes).

Middleware Ordering

The position of persist() in your middleware chain matters.

Recommended order: Place persist() towards the end of the chain. This ensures that it only saves the "final" state after other middleware (like validate or debounce) have had their chance to process the update.

const store = pipe(
  validate(schema),
  persist({ local: 'app-state' })
)({ count: 0 });

On this page