An abstract persistence layer for your reactive state. Supports storage mocking, custom serializers/deserializers, migrations and storage subscriptions.
Check out @reatom/persist-web-storage
for adapters for localStorage
and sessionStorage
.
First of all, you need a persistence adapter. Every adapter is an operator which you can apply to an atom to persist its value. Most likely, the adapter you want is already implemented in withLocalStorage
from @reatom/persist-web-storage
. reatomPersist
function can be used to create a custom persist adapter.
To create a custom persist adapter, implement the following interface:
export const reatomPersist = (
storageAtom: AtomMut < PersistStorage >
export interface WithPersist {
options : string | WithPersistOptions < AtomState < T >>
export interface PersistStorage {
get ( ctx : Ctx , key : string ) : PersistRecord | null
set ( ctx : Ctx , key : string , rec : PersistRecord ) : void
clear ? ( ctx : Ctx , key : string ) : void
subscribe ? ( ctx : Ctx , key : string , callback : Fn <[]> ) : Unsubscribe
export interface PersistRecord< T = unknown > {
See createMemStorage
for an example of PersistStorage
implementation.
Every adapter accepts the following set of options. Passing a string is identical to only passing the key
option.
export interface WithPersistOptions< T > {
* Key of the storage record.
* Custom snapshot serializer.
toSnapshot ?: Fn <[ctx: Ctx , state: T ], unknown >
* Custom snapshot deserializer.
fromSnapshot ?: Fn <[ctx: Ctx , snapshot: unknown , state ? : T ], T >
* A callback to call if the version of a stored snapshot is older than `version` option.
migration ?: Fn <[ctx: Ctx , persistRecord: PersistRecord ], T >
* Determines whether the atom is updated on storage updates.
* Number of milliseconds from the snapshot creation time after which it will be deleted.
* @defaultValue MAX_SAFE_TIMEOUT
* Version of the stored snapshot. Triggers `migration`.
Every persist adapter has the storageAtom
atom which allows you to mock an adapter’s storage when testing persisted atoms. createMemStorage
function can be used to create such mocked storage.
import { atom } from ' @reatom/framework '
import { withLocalStorage } from ' @reatom/persist-web-storage '
export const tokenAtom = atom ( '' , ' tokenAtom ' ) . pipe ( withLocalStorage ( ' token ' ))
import { test } from ' uvu '
import * as assert from ' uvu/assert '
import { createTestCtx } from ' @reatom/testing '
import { createMemStorage } from ' @reatom/persist '
import { withLocalStorage } from ' @reatom/persist-web-storage '
import { tokenAtom } from ' ./feature '
const ctx = createTestCtx ()
const mockStorage = createMemStorage ( { token: ' 123 ' } )
withLocalStorage . storageAtom (ctx, mockStorage)
assert . is (ctx . get (tokenAtom), ' 123 ' )
A fully-featured SSR example with Next.js can be found here↗ .
The example below shows how simple it is to implement an SSR adapter. To do so, create an in-memory storage with createMemStorage
, use it to persist your atoms, and populate it before rendering the app.
import { createMemStorage, reatomPersist } from ' @reatom/persist '
const ssrStorage = createMemStorage ( { name: ' ssr ' , subscribe: false } )
export const { snapshotAtom } = ssrStorage
export const withSsr = reatomPersist (ssrStorage)
import { atom } from ' @reatom/core '
import { withSsr } from ' src/ssr '
export const filtersAtom = atom ( '' ) . pipe ( withSsr ( ' goods/filters ' ))
export const listAtom = atom ( new Map ()) . pipe (
toSnapshot : ( ctx , list ) => [ ... list] ,
fromSnapshot : ( ctx , snapshot ) => new Map (snapshot) ,
import { createCtx } from ' @reatom/core '
import { snapshotAtom } from ' src/ssr '
export const ssrHandler = async () => {
await doAsyncStuffToFillTheState (ctx)
const snapshot = ctx . get (snapshotAtom)
export const render = ( { snapshot } ) => {
export const ctx = createCtx ()
snapshotAtom (ctx , snapshot)
runFeaturesAndRenderTheApp (ctx)