1 | <p align="center">
|
2 | <a href="https://xstate.js.org">
|
3 | <br />
|
4 | <img src="https://i.imgur.com/FshbFOv.png" alt="XState" width="100"/>
|
5 | <br />
|
6 | <sub><strong>JavaScript state machines and statecharts</strong></sub>
|
7 | <br />
|
8 | <br />
|
9 | </a>
|
10 | </p>
|
11 |
|
12 | [](https://badge.fury.io/js/xstate)
|
13 | [](https://gitter.im/statecharts/statecharts)
|
14 | <img src="https://opencollective.com/xstate/tiers/backer/badge.svg?label=sponsors&color=brightgreen" />
|
15 |
|
16 | JavaScript and TypeScript [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine) and [statecharts](https://www.sciencedirect.com/science/article/pii/0167642387900359/pdf) for the modern web.
|
17 |
|
18 | 📖 [Read the documentation](https://xstate.js.org/docs)
|
19 | 📑 Adheres to the [SCXML specification](https://www.w3.org/TR/scxml/).
|
20 |
|
21 | ## Packages
|
22 |
|
23 | - 🤖 `xstate` - Core finite state machine and statecharts library + interpreter
|
24 | - [🔬 `@xstate/fsm`](https://github.com/statelyai/xstate/tree/main/packages/xstate-fsm) - Minimal finite state machine library
|
25 | - [📉 `@xstate/graph`](https://github.com/statelyai/xstate/tree/main/packages/xstate-graph) - Graph traversal utilities for XState
|
26 | - [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) - React hooks and utilities for using XState in React applications
|
27 | - [✅ `@xstate/test`](https://github.com/statelyai/xstate/tree/main/packages/xstate-test) - Model-based testing utilities for XState
|
28 |
|
29 | ## Super quick start
|
30 |
|
31 | ```bash
|
32 | npm install xstate
|
33 | ```
|
34 |
|
35 | ```js
|
36 | import { createMachine, interpret } from 'xstate';
|
37 |
|
38 | // Stateless machine definition
|
39 | // machine.transition(...) is a pure function used by the interpreter.
|
40 | const toggleMachine = createMachine({
|
41 | id: 'toggle',
|
42 | initial: 'inactive',
|
43 | states: {
|
44 | inactive: { on: { TOGGLE: 'active' } },
|
45 | active: { on: { TOGGLE: 'inactive' } }
|
46 | }
|
47 | });
|
48 |
|
49 | // Machine instance with internal state
|
50 | const toggleService = interpret(toggleMachine)
|
51 | .onTransition((state) => console.log(state.value))
|
52 | .start();
|
53 | // => 'inactive'
|
54 |
|
55 | toggleService.send('TOGGLE');
|
56 | // => 'active'
|
57 |
|
58 | toggleService.send('TOGGLE');
|
59 | // => 'inactive'
|
60 | ```
|
61 |
|
62 | - [Visualizer](#visualizer)
|
63 | - [Why? (info about statecharts)](#why)
|
64 | - [Installation](https://xstate.js.org/docs/guides/installation.html)
|
65 | - [Finite State Machines](#finite-state-machines)
|
66 | - [Hierarchical (Nested) State Machines](#hierarchical--nested--state-machines)
|
67 | - [Parallel State Machines](#parallel-state-machines)
|
68 | - [History States](#history-states)
|
69 |
|
70 | ## Visualizer
|
71 |
|
72 | **[Visualize, simulate, and share your statecharts in XState Viz!](https://stately.ai/viz)**
|
73 |
|
74 | <a href="https://stately.ai/viz" title="xstate visualizer"><img src="https://i.imgur.com/3pEB0B3.png" alt="xstate visualizer" width="300" /></a>
|
75 |
|
76 | ## Why?
|
77 |
|
78 | Statecharts are a formalism for modeling stateful, reactive systems. This is useful for declaratively describing the _behavior_ of your application, from the individual components to the overall application logic.
|
79 |
|
80 | Read [📽 the slides](http://slides.com/davidkhourshid/finite-state-machines) ([🎥 video](https://www.youtube.com/watch?v=VU1NKX6Qkxc)) or check out these resources for learning about the importance of finite state machines and statecharts in user interfaces:
|
81 |
|
82 | - [Statecharts - A Visual Formalism for Complex Systems](https://www.sciencedirect.com/science/article/pii/0167642387900359/pdf) by David Harel
|
83 | - [The World of Statecharts](https://statecharts.github.io/) by Erik Mogensen
|
84 | - [Pure UI](https://rauchg.com/2015/pure-ui) by Guillermo Rauch
|
85 | - [Pure UI Control](https://medium.com/@asolove/pure-ui-control-ac8d1be97a8d) by Adam Solove
|
86 | - [Stately Discord](https://discord.gg/xstate) chat about anything related to statecharts and XState
|
87 | - [GitHub Discussions](https://github.com/statelyai/xstate/discussions)
|
88 |
|
89 | ## Finite State Machines
|
90 |
|
91 | <img src="https://imgur.com/rqqmkJh.png" alt="Light Machine" width="300" />
|
92 |
|
93 | ```js
|
94 | import { createMachine } from 'xstate';
|
95 |
|
96 | const lightMachine = createMachine({
|
97 | id: 'light',
|
98 | initial: 'green',
|
99 | states: {
|
100 | green: {
|
101 | on: {
|
102 | TIMER: 'yellow'
|
103 | }
|
104 | },
|
105 | yellow: {
|
106 | on: {
|
107 | TIMER: 'red'
|
108 | }
|
109 | },
|
110 | red: {
|
111 | on: {
|
112 | TIMER: 'green'
|
113 | }
|
114 | }
|
115 | }
|
116 | });
|
117 |
|
118 | const currentState = 'green';
|
119 |
|
120 | const nextState = lightMachine.transition(currentState, 'TIMER').value;
|
121 |
|
122 | // => 'yellow'
|
123 | ```
|
124 |
|
125 | ## Hierarchical (Nested) State Machines
|
126 |
|
127 | <img src="https://imgur.com/GDZAeB9.png" alt="Hierarchical Light Machine" width="300" />
|
128 |
|
129 | ```js
|
130 | import { createMachine } from 'xstate';
|
131 |
|
132 | const pedestrianStates = {
|
133 | initial: 'walk',
|
134 | states: {
|
135 | walk: {
|
136 | on: {
|
137 | PED_TIMER: 'wait'
|
138 | }
|
139 | },
|
140 | wait: {
|
141 | on: {
|
142 | PED_TIMER: 'stop'
|
143 | }
|
144 | },
|
145 | stop: {}
|
146 | }
|
147 | };
|
148 |
|
149 | const lightMachine = createMachine({
|
150 | id: 'light',
|
151 | initial: 'green',
|
152 | states: {
|
153 | green: {
|
154 | on: {
|
155 | TIMER: 'yellow'
|
156 | }
|
157 | },
|
158 | yellow: {
|
159 | on: {
|
160 | TIMER: 'red'
|
161 | }
|
162 | },
|
163 | red: {
|
164 | on: {
|
165 | TIMER: 'green'
|
166 | },
|
167 | ...pedestrianStates
|
168 | }
|
169 | }
|
170 | });
|
171 |
|
172 | const currentState = 'yellow';
|
173 |
|
174 | const nextState = lightMachine.transition(currentState, 'TIMER').value;
|
175 | // => {
|
176 | // red: 'walk'
|
177 | // }
|
178 |
|
179 | lightMachine.transition('red.walk', 'PED_TIMER').value;
|
180 | // => {
|
181 | // red: 'wait'
|
182 | // }
|
183 | ```
|
184 |
|
185 | **Object notation for hierarchical states:**
|
186 |
|
187 | ```js
|
188 | // ...
|
189 | const waitState = lightMachine.transition({ red: 'walk' }, 'PED_TIMER').value;
|
190 |
|
191 | // => { red: 'wait' }
|
192 |
|
193 | lightMachine.transition(waitState, 'PED_TIMER').value;
|
194 |
|
195 | // => { red: 'stop' }
|
196 |
|
197 | lightMachine.transition({ red: 'stop' }, 'TIMER').value;
|
198 |
|
199 | // => 'green'
|
200 | ```
|
201 |
|
202 | ## Parallel State Machines
|
203 |
|
204 | <img src="https://imgur.com/GKd4HwR.png" width="300" alt="Parallel state machine" />
|
205 |
|
206 | ```js
|
207 | const wordMachine = createMachine({
|
208 | id: 'word',
|
209 | type: 'parallel',
|
210 | states: {
|
211 | bold: {
|
212 | initial: 'off',
|
213 | states: {
|
214 | on: {
|
215 | on: { TOGGLE_BOLD: 'off' }
|
216 | },
|
217 | off: {
|
218 | on: { TOGGLE_BOLD: 'on' }
|
219 | }
|
220 | }
|
221 | },
|
222 | underline: {
|
223 | initial: 'off',
|
224 | states: {
|
225 | on: {
|
226 | on: { TOGGLE_UNDERLINE: 'off' }
|
227 | },
|
228 | off: {
|
229 | on: { TOGGLE_UNDERLINE: 'on' }
|
230 | }
|
231 | }
|
232 | },
|
233 | italics: {
|
234 | initial: 'off',
|
235 | states: {
|
236 | on: {
|
237 | on: { TOGGLE_ITALICS: 'off' }
|
238 | },
|
239 | off: {
|
240 | on: { TOGGLE_ITALICS: 'on' }
|
241 | }
|
242 | }
|
243 | },
|
244 | list: {
|
245 | initial: 'none',
|
246 | states: {
|
247 | none: {
|
248 | on: { BULLETS: 'bullets', NUMBERS: 'numbers' }
|
249 | },
|
250 | bullets: {
|
251 | on: { NONE: 'none', NUMBERS: 'numbers' }
|
252 | },
|
253 | numbers: {
|
254 | on: { BULLETS: 'bullets', NONE: 'none' }
|
255 | }
|
256 | }
|
257 | }
|
258 | }
|
259 | });
|
260 |
|
261 | const boldState = wordMachine.transition('bold.off', 'TOGGLE_BOLD').value;
|
262 |
|
263 | // {
|
264 | // bold: 'on',
|
265 | // italics: 'off',
|
266 | // underline: 'off',
|
267 | // list: 'none'
|
268 | // }
|
269 |
|
270 | const nextState = wordMachine.transition(
|
271 | {
|
272 | bold: 'off',
|
273 | italics: 'off',
|
274 | underline: 'on',
|
275 | list: 'bullets'
|
276 | },
|
277 | 'TOGGLE_ITALICS'
|
278 | ).value;
|
279 |
|
280 | // {
|
281 | // bold: 'off',
|
282 | // italics: 'on',
|
283 | // underline: 'on',
|
284 | // list: 'bullets'
|
285 | // }
|
286 | ```
|
287 |
|
288 | ## History States
|
289 |
|
290 | <img src="https://imgur.com/I4QsQsz.png" width="300" alt="Machine with history state" />
|
291 |
|
292 | ```js
|
293 | const paymentMachine = createMachine({
|
294 | id: 'payment',
|
295 | initial: 'method',
|
296 | states: {
|
297 | method: {
|
298 | initial: 'cash',
|
299 | states: {
|
300 | cash: { on: { SWITCH_CHECK: 'check' } },
|
301 | check: { on: { SWITCH_CASH: 'cash' } },
|
302 | hist: { type: 'history' }
|
303 | },
|
304 | on: { NEXT: 'review' }
|
305 | },
|
306 | review: {
|
307 | on: { PREVIOUS: 'method.hist' }
|
308 | }
|
309 | }
|
310 | });
|
311 |
|
312 | const checkState = paymentMachine.transition('method.cash', 'SWITCH_CHECK');
|
313 |
|
314 | // => State {
|
315 | // value: { method: 'check' },
|
316 | // history: State { ... }
|
317 | // }
|
318 |
|
319 | const reviewState = paymentMachine.transition(checkState, 'NEXT');
|
320 |
|
321 | // => State {
|
322 | // value: 'review',
|
323 | // history: State { ... }
|
324 | // }
|
325 |
|
326 | const previousState = paymentMachine.transition(reviewState, 'PREVIOUS').value;
|
327 |
|
328 | // => { method: 'check' }
|
329 | ```
|
330 |
|
331 | ## Sponsors
|
332 |
|
333 | Huge thanks to the following companies for sponsoring `xstate`. You can sponsor further `xstate` development [on OpenCollective](https://opencollective.com/xstate).
|
334 |
|
335 | <a href="https://tipe.io" title="Tipe.io"><img src="https://cdn.tipe.io/tipe/tipe-logo.svg?w=240" style="background:#613DEF" /></a>
|
336 | <a href="https://webflow.com" title="Webflow"><img src="https://uploads-ssl.webflow.com/583347ca8f6c7ee058111b3b/5b03bde0971fdd75d75b5591_webflow.png" height="100" /></a>
|