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 | [![npm version](https://badge.fury.io/js/xstate.svg)](https://badge.fury.io/js/xstate)
|
13 | [![Statecharts gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](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 | - [Spectrum - Statecharts Community](https://spectrum.chat/statecharts) (For XState specific questions, please use the [GitHub Discussions](https://github.com/statelyai/xstate/discussions))
|
87 |
|
88 | ## Finite State Machines
|
89 |
|
90 | <img src="https://imgur.com/rqqmkJh.png" alt="Light Machine" width="300" />
|
91 |
|
92 | ```js
|
93 | import { createMachine } from 'xstate';
|
94 |
|
95 | const lightMachine = createMachine({
|
96 | id: 'light',
|
97 | initial: 'green',
|
98 | states: {
|
99 | green: {
|
100 | on: {
|
101 | TIMER: 'yellow'
|
102 | }
|
103 | },
|
104 | yellow: {
|
105 | on: {
|
106 | TIMER: 'red'
|
107 | }
|
108 | },
|
109 | red: {
|
110 | on: {
|
111 | TIMER: 'green'
|
112 | }
|
113 | }
|
114 | }
|
115 | });
|
116 |
|
117 | const currentState = 'green';
|
118 |
|
119 | const nextState = lightMachine.transition(currentState, 'TIMER').value;
|
120 |
|
121 | // => 'yellow'
|
122 | ```
|
123 |
|
124 | ## Hierarchical (Nested) State Machines
|
125 |
|
126 | <img src="https://imgur.com/GDZAeB9.png" alt="Hierarchical Light Machine" width="300" />
|
127 |
|
128 | ```js
|
129 | import { createMachine } from 'xstate';
|
130 |
|
131 | const pedestrianStates = {
|
132 | initial: 'walk',
|
133 | states: {
|
134 | walk: {
|
135 | on: {
|
136 | PED_TIMER: 'wait'
|
137 | }
|
138 | },
|
139 | wait: {
|
140 | on: {
|
141 | PED_TIMER: 'stop'
|
142 | }
|
143 | },
|
144 | stop: {}
|
145 | }
|
146 | };
|
147 |
|
148 | const lightMachine = createMachine({
|
149 | id: 'light',
|
150 | initial: 'green',
|
151 | states: {
|
152 | green: {
|
153 | on: {
|
154 | TIMER: 'yellow'
|
155 | }
|
156 | },
|
157 | yellow: {
|
158 | on: {
|
159 | TIMER: 'red'
|
160 | }
|
161 | },
|
162 | red: {
|
163 | on: {
|
164 | TIMER: 'green'
|
165 | },
|
166 | ...pedestrianStates
|
167 | }
|
168 | }
|
169 | });
|
170 |
|
171 | const currentState = 'yellow';
|
172 |
|
173 | const nextState = lightMachine.transition(currentState, 'TIMER').value;
|
174 | // => {
|
175 | // red: 'walk'
|
176 | // }
|
177 |
|
178 | lightMachine.transition('red.walk', 'PED_TIMER').value;
|
179 | // => {
|
180 | // red: 'wait'
|
181 | // }
|
182 | ```
|
183 |
|
184 | **Object notation for hierarchical states:**
|
185 |
|
186 | ```js
|
187 | // ...
|
188 | const waitState = lightMachine.transition({ red: 'walk' }, 'PED_TIMER').value;
|
189 |
|
190 | // => { red: 'wait' }
|
191 |
|
192 | lightMachine.transition(waitState, 'PED_TIMER').value;
|
193 |
|
194 | // => { red: 'stop' }
|
195 |
|
196 | lightMachine.transition({ red: 'stop' }, 'TIMER').value;
|
197 |
|
198 | // => 'green'
|
199 | ```
|
200 |
|
201 | ## Parallel State Machines
|
202 |
|
203 | <img src="https://imgur.com/GKd4HwR.png" width="300" alt="Parallel state machine" />
|
204 |
|
205 | ```js
|
206 | const wordMachine = createMachine({
|
207 | id: 'word',
|
208 | type: 'parallel',
|
209 | states: {
|
210 | bold: {
|
211 | initial: 'off',
|
212 | states: {
|
213 | on: {
|
214 | on: { TOGGLE_BOLD: 'off' }
|
215 | },
|
216 | off: {
|
217 | on: { TOGGLE_BOLD: 'on' }
|
218 | }
|
219 | }
|
220 | },
|
221 | underline: {
|
222 | initial: 'off',
|
223 | states: {
|
224 | on: {
|
225 | on: { TOGGLE_UNDERLINE: 'off' }
|
226 | },
|
227 | off: {
|
228 | on: { TOGGLE_UNDERLINE: 'on' }
|
229 | }
|
230 | }
|
231 | },
|
232 | italics: {
|
233 | initial: 'off',
|
234 | states: {
|
235 | on: {
|
236 | on: { TOGGLE_ITALICS: 'off' }
|
237 | },
|
238 | off: {
|
239 | on: { TOGGLE_ITALICS: 'on' }
|
240 | }
|
241 | }
|
242 | },
|
243 | list: {
|
244 | initial: 'none',
|
245 | states: {
|
246 | none: {
|
247 | on: { BULLETS: 'bullets', NUMBERS: 'numbers' }
|
248 | },
|
249 | bullets: {
|
250 | on: { NONE: 'none', NUMBERS: 'numbers' }
|
251 | },
|
252 | numbers: {
|
253 | on: { BULLETS: 'bullets', NONE: 'none' }
|
254 | }
|
255 | }
|
256 | }
|
257 | }
|
258 | });
|
259 |
|
260 | const boldState = wordMachine.transition('bold.off', 'TOGGLE_BOLD').value;
|
261 |
|
262 | // {
|
263 | // bold: 'on',
|
264 | // italics: 'off',
|
265 | // underline: 'off',
|
266 | // list: 'none'
|
267 | // }
|
268 |
|
269 | const nextState = wordMachine.transition(
|
270 | {
|
271 | bold: 'off',
|
272 | italics: 'off',
|
273 | underline: 'on',
|
274 | list: 'bullets'
|
275 | },
|
276 | 'TOGGLE_ITALICS'
|
277 | ).value;
|
278 |
|
279 | // {
|
280 | // bold: 'off',
|
281 | // italics: 'on',
|
282 | // underline: 'on',
|
283 | // list: 'bullets'
|
284 | // }
|
285 | ```
|
286 |
|
287 | ## History States
|
288 |
|
289 | <img src="https://imgur.com/I4QsQsz.png" width="300" alt="Machine with history state" />
|
290 |
|
291 | ```js
|
292 | const paymentMachine = createMachine({
|
293 | id: 'payment',
|
294 | initial: 'method',
|
295 | states: {
|
296 | method: {
|
297 | initial: 'cash',
|
298 | states: {
|
299 | cash: { on: { SWITCH_CHECK: 'check' } },
|
300 | check: { on: { SWITCH_CASH: 'cash' } },
|
301 | hist: { type: 'history' }
|
302 | },
|
303 | on: { NEXT: 'review' }
|
304 | },
|
305 | review: {
|
306 | on: { PREVIOUS: 'method.hist' }
|
307 | }
|
308 | }
|
309 | });
|
310 |
|
311 | const checkState = paymentMachine.transition('method.cash', 'SWITCH_CHECK');
|
312 |
|
313 | // => State {
|
314 | // value: { method: 'check' },
|
315 | // history: State { ... }
|
316 | // }
|
317 |
|
318 | const reviewState = paymentMachine.transition(checkState, 'NEXT');
|
319 |
|
320 | // => State {
|
321 | // value: 'review',
|
322 | // history: State { ... }
|
323 | // }
|
324 |
|
325 | const previousState = paymentMachine.transition(reviewState, 'PREVIOUS').value;
|
326 |
|
327 | // => { method: 'check' }
|
328 | ```
|
329 |
|
330 | ## Sponsors
|
331 |
|
332 | Huge thanks to the following companies for sponsoring `xstate`. You can sponsor further `xstate` development [on OpenCollective](https://opencollective.com/xstate).
|
333 |
|
334 | <a href="https://tipe.io" title="Tipe.io"><img src="https://cdn.tipe.io/tipe/tipe-logo.svg?w=240" style="background:#613DEF" /></a>
|
335 | <a href="https://webflow.com" title="Webflow"><img src="https://uploads-ssl.webflow.com/583347ca8f6c7ee058111b3b/5b03bde0971fdd75d75b5591_webflow.png" height="100" /></a>
|