Skip to content

Commit 51734e5

Browse files
authored
Merge pull request #206 from statelyai/davidkpiano/persisting-restoring-state
Persisting and restoring state
2 parents 54167e8 + ed64016 commit 51734e5

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
---
2+
title: 'Persisting and restoring state in XState'
3+
description: 'Learn how to persist and restore your state machine state in XState'
4+
tags:
5+
[
6+
state machine,
7+
statechart,
8+
state,
9+
persist,
10+
restore,
11+
business logic,
12+
workflow,
13+
xstate,
14+
]
15+
authors: [david]
16+
date: 2023-10-02
17+
slug: 2023-10-02-persisting-state
18+
image: /blog/2023-10-02-persisting-state.png
19+
---
20+
21+
State machines are great for modeling state in applications. However, we often need to persist and restore state across sessions - for example, when a user closes and reopens their browser. In this article, we'll explore how to persist and restore state in XState so your frontend applications or backend workflows can pick up where it left off.
22+
23+
<!--truncate-->
24+
25+
:::info
26+
The code in this article applies to XState v5 beta.
27+
:::
28+
29+
## Quick reference
30+
31+
If you're already familiar with XState, here's a quick reference for persisting and restoring state:
32+
33+
```ts
34+
import { createActor } from 'xstate';
35+
import { someMachine } from './someMachine';
36+
37+
// Get the state from localStorage (if it exists)
38+
const stateString = localStorage.getItem('some-state');
39+
40+
// Create the state from the string (if it exists)
41+
const restoredState = stateString ? JSON.parse(stateString) : undefined;
42+
43+
const actor = createActor(machine, {
44+
// Restore the state (if it exists)
45+
state: restoredState,
46+
});
47+
48+
actor.subscribe(() => {
49+
// Persist the state to localStorage
50+
const persistedState = actor.getPersistedState();
51+
localStorage.setItem('some-state', JSON.stringify(persistedState));
52+
});
53+
54+
actor.start();
55+
```
56+
57+
## Persisting and restoring state
58+
59+
Let's say we have a state machine that represents a user's shopping cart:
60+
61+
```ts
62+
import { createMachine, createActor, assign } from 'xstate';
63+
64+
const checkoutMachine = createMachine({
65+
id: 'checkout',
66+
initial: 'shopping',
67+
context: {
68+
items: [],
69+
},
70+
states: {
71+
shopping: {
72+
on: {
73+
'item.add': {
74+
actions: assign({
75+
items: ({ context, event }) => {
76+
return [...context.items, event.item];
77+
},
78+
}),
79+
},
80+
checkout: 'checkingOut',
81+
},
82+
},
83+
checkingOut: {
84+
// ...
85+
},
86+
},
87+
});
88+
89+
const checkoutActor = createActor(machine, {
90+
// ...
91+
});
92+
```
93+
94+
You may want to persist the state of this machine so that when the user comes back to the site, the items in their cart are still there, and the step in the checkout process is still the same. To do this, we need to remember to do two things:
95+
96+
- **Persist the state** to some storage (e.g., localStorage, a database, etc.)
97+
- **Restore the state** from the storage when creating the actor
98+
99+
First, you should determine your [persistence strategy](#persistence-strategies). For this example, we'll use [the `localStorage` API](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
100+
101+
Actors created with `createActor` have a `.getPersistedState()` method that returns the state that can be persisted. This state is a plain object that can be serialized to JSON using `JSON.stringify(persistedState)`. Since the persisted state is a plain object, you can persist it to any storage that you want, as long as it can be restored as an object.
102+
103+
```ts
104+
// Read the state to persist from the actor (sync)
105+
const persistedState = actor.getPersistedState();
106+
107+
// Persist the state to localStorage
108+
localStorage.setItem('some-state-key', JSON.stringify(persistedState));
109+
```
110+
111+
Retrieve the persisted state from storage and restore it as an object when creating the actor:
112+
113+
```ts
114+
// Read the persisted state from localStorage
115+
const restoredState = localStorage.getItem('some-state-key');
116+
117+
const actor = createActor(machine, {
118+
// Restore the state (if it exists)
119+
state: restoredState,
120+
});
121+
122+
actor.start();
123+
```
124+
125+
The actor will start at the `restoredState`, if it exists. If it is `undefined`, the actor will start at the initial state of the actor logic provided to `createActor(logic)`.
126+
127+
## Persistence strategies
128+
129+
For the browser, you can:
130+
131+
- Use [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)
132+
- Use a client-side database like [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
133+
- Use [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
134+
- Use [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)
135+
- Store it in runtime memory (e.g., a global variable or some other persistent store)
136+
137+
For the server, you can:
138+
139+
- Use a database (e.g., [MongoDB](https://www.mongodb.com/), [PostgreSQL](https://www.postgresql.org/), etc.)
140+
- Use a cache (e.g., [Redis](https://redis.io/))
141+
- Use cookies or session storage
142+
143+
## Examples
144+
145+
There are examples of persisting state that can be found in the [XState git repository](https://github.com/statelyai/xstate/tree/next/examples):
146+
147+
- [Persisting state to a writable `.json` file](https://github.com/statelyai/xstate/blob/next/examples/persisted-donut-maker)
148+
- [Persisting state to MongoDB](https://github.com/statelyai/xstate/tree/next/examples/mongodb-persisted-state)
149+
150+
Feel free to suggest other examples at [feedback.stately.ai](https://feedback.stately.ai/examples), or [contribute your own](https://github.com/statelyai/xstate/tree/next/examples)!
286 KB
Loading

0 commit comments

Comments
 (0)