Skip to content
Version: XState v5

Persistence

Actors can persist their internal state and restore it later. Persistence refers to storing the state of an actor in persistent storage, such as localStorage or a database. Restoration refers to restoring the state of an actor from persistent storage.

In frontend applications, persistence is useful for maintaining state across browser reloads. In backend applications, persistence allows workflows to span multiple requests, survive service restarts, be fault-tolerant, represent long-running processes, and be auditable & traceable.

In XState, you can obtain the state to be persisted via actor.getPersistedState() and restore state via interpret(behavior, { state: restoredState }).start():

const feedbackActor = interpret(feedbackMachine).start();

// Get state to be persisted
const persistedState = feedbackActor.getPersistedState();

// Persist state
localStorage.setItem('feedback', JSON.stringify(persistedState));

// Restore state
const restoredState = JSON.parse(localStorage.getItem('feedback'));

const restoredFeedbackActor = interpret(feedbackMachine, {
state: restoredState,
}).start();

Persisting state​

You can obtain the state to be persisted via actor.getPersistedState():

const feedbackActor = interpret(feedbackMachine).start();

// Get state to be persisted
const persistedState = feedbackActor.getPersistedState();

The internal state can be persisted from any actor, not only machines. Note that the persisted state is not the same as the snapshot from actor.getSnapshot(); persisted state represents the internal state of the actor, while snapshots represent the actor's last emitted value:

const promiseActor = fromPromise(() => Promise.resolve(42));

// Get the last emitted value
const snapshot = promiseActor.getSnapshot();
console.log(snapshot);
// logs 42

// Get the persisted state
const persistedState = promiseActor.getPersistedState();
console.log(persistedState);
// logs { status: 'done', data: 42 }

Restoring state​

You can restore an actor to a persisted state by passing the persisted state into the state option of the 2nd argument of interpret(logic, { state: restoredState }):

// Get persisted state
const restoredState = JSON.parse(localStorage.getItem('feedback'));

// Restore state
const feedbackActor = interpret(feedbackMachine, {
state: restoredState,
});

feedbackActor.start();

Actions from machine actors will not be re-executed, because they are assumed to have been already executed. However, invocations will be restarted, and spawned actors will be restored recursively.

Deep persistence​

Persisting & restoring state from machine actors is deep; all invoked & spawned actors will be persisted & restored recursively.

const feedbackMachine = createMachine({
// ...
states: {
form: {
invoke: {
id: 'form',
src: formMachine,
},
},
},
});

const feedbackActor = interpret(feedbackMachine).start();

// Persist state
const persistedState = feedbackActor.getPersistedState();
localStorage.setItem('feedback', JSON.stringify(persistedState));

// ...

// Restore state
const restoredState = JSON.parse(localStorage.getItem('feedback'));

const restoredFeedbackActor = interpret(feedbackMachine, {
state: restoredState,
}).start();
// Will restore both the feedbackActor and the invoked form actor at
// their persisted states

Event sourcing​

An alternative to persisting state is event sourcing, which is a way of restoring the state of an actor by replaying the events that led to that state. Event sourcing can be more reliable than persisting state, because it is less prone to incompatible state.

To implement event sourcing, you persist the events as they happen, and then replay them to restore the state of the actor:

Code example coming soon

  • Useful for replaying actions

Strategies​

  • Storage
    • Localstorage
    • DB
    • Cookies
    • Filesystem
    • URL

Caveats​

There are some caveats to persisting & restoring state that you should be aware of:

  • Incompatible state: if the machine or actor logic changes, the restored state may be incompatible with the new logic.
  • Replaying actions: actions that have already been executed will not be re-executed. [Event sourcing] is preferred for this use-case.
  • Serialization: the state must be serializable, which means that it must be JSON-serializable. This means that you cannot persist functions, classes, or other non-serializable values.

TypeScript​

Coming soon

Cheatsheet​

Persisting state

const persistedState = actor.getPersistedState();

Restoring state

const restoredState = JSON.parse(localStorage.getItem('feedback'));

const restoredActor = interpret(actorMachine, {
state: restoredState,
}).start();

Resources​

Coming soon