Security News
ESLint is Now Language-Agnostic: Linting JSON, Markdown, and Beyond
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
The xstate npm package is a library for creating, interpreting, and executing finite state machines and statecharts, as well as managing invocations of those machines as actors. It provides a robust framework for modeling and analyzing application logic in a composable and declarative way, which can help manage complex state logic in UIs, robotics, and other systems.
Finite State Machines
This feature allows you to create simple finite state machines. The code sample demonstrates a toggle machine with two states: 'active' and 'inactive'.
{"import { Machine } from 'xstate';\n\nconst toggleMachine = Machine({\n id: 'toggle',\n initial: 'inactive',\n states: {\n inactive: { on: { TOGGLE: 'active' } },\n active: { on: { TOGGLE: 'inactive' } }\n }\n});"}
Statecharts
This feature allows you to create statecharts, which are an extension of state machines that can have hierarchical and parallel states. The code sample shows a traffic light machine with three states: 'green', 'yellow', and 'red'.
{"import { Machine } from 'xstate';\n\nconst lightMachine = Machine({\n id: 'light',\n initial: 'green',\n states: {\n green: { on: { TIMER: 'yellow' } },\n yellow: { on: { TIMER: 'red' } },\n red: { on: { TIMER: 'green' } }\n }\n});"}
Interpreters
This feature allows you to interpret and execute the state machines and statecharts. The code sample demonstrates how to start a service that interprets the toggleMachine and logs state transitions.
{"import { interpret } from 'xstate';\n\nconst service = interpret(toggleMachine).onTransition((state) => console.log(state.value));\n\nservice.start();\nservice.send('TOGGLE');\nservice.send('TOGGLE');"}
Actors
This feature allows you to manage machine invocations as actors, which can send and receive events from other machines. The code sample shows how to spawn a child machine within a parent machine's context.
{"import { spawn, Machine } from 'xstate';\n\nconst parentMachine = Machine({\n context: {\n child: null\n },\n entry: ['spawnChild']\n}, {\n actions: {\n spawnChild: assign({\n child: () => spawn(someMachine)\n })\n }\n});"}
robot3 is a functional, immutable finite state machine library with a similar API to xstate. It is smaller in size but does not offer the full range of features such as hierarchical and parallel states.
state-machine-cat allows you to visualize state machines and statecharts. While it does not execute state machines like xstate, it is useful for documentation and analysis purposes.
redux-saga is a library that handles side effects in Redux applications using sagas, which are similar to state machines. It is more focused on managing side effects than modeling state, unlike xstate which is more general-purpose.
Simple, stateless JavaScript finite state machines and statecharts.
Read the slides (video) or check out these resources for learning about the importance of finite state machines and statecharts in user interfaces:
onEnter
, onTransition
, and onExit
hooksThe JSON-based notation used here to declaratively represent finite state machines and statecharts can be copy-pasted here: https://codepen.io/davidkpiano/pen/ayWKJO/ which will generate interactive state transition diagrams.
npm install xstate --save
import { Machine } from 'xstate';
import { Machine } from 'xstate';
const lightMachine = Machine({
key: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow',
}
},
yellow: {
on: {
TIMER: 'red',
}
},
red: {
on: {
TIMER: 'green',
}
}
}
});
const currentState = 'green';
const nextState = lightMachine
.transition(currentState, 'TIMER')
.value;
// => 'yellow'
import { Machine } from 'xstate';
const pedestrianStates = {
initial: 'walk',
states: {
walk: {
on: {
PED_TIMER: 'wait'
}
},
wait: {
on: {
PED_TIMER: 'stop'
}
},
stop: {}
}
};
const lightMachine = Machine({
key: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
},
...pedestrianStates
}
}
});
const currentState = 'yellow';
const nextState = lightMachine
.transition(currentState, 'TIMER')
.toString(); // toString() only works for non-parallel machines
// => 'red.walk'
lightMachine
.transition('red.walk', 'PED_TIMER')
.toString();
// => 'red.wait'
Object notation for hierarchical states:
// ...
const waitState = lightMachine
.transition('red.walk', 'PED_TIMER')
.value;
// => { red: 'wait' }
lightMachine
.transition(waitState, 'PED_TIMER')
.value;
// => { red: 'stop' }
lightMachine
.transition('red.stop', 'TIMER')
.value;
// => 'green'
const wordMachine = Machine({
parallel: true,
states: {
bold: {
initial: 'off',
states: {
on: {
on: { TOGGLE_BOLD: 'off' }
},
off: {
on: { TOGGLE_BOLD: 'on' }
}
}
},
underline: {
initial: 'off',
states: {
on: {
on: { TOGGLE_UNDERLINE: 'off' }
},
off: {
on: { TOGGLE_UNDERLINE: 'on' }
}
}
},
italics: {
initial: 'off',
states: {
on: {
on: { TOGGLE_ITALICS: 'off' }
},
off: {
on: { TOGGLE_ITALICS: 'on' }
}
}
},
list: {
initial: 'none',
states: {
none: {
on: { BULLETS: 'bullets', NUMBERS: 'numbers' }
},
bullets: {
on: { NONE: 'none', NUMBERS: 'numbers' }
},
numbers: {
on: { BULLETS: 'bullets', NONE: 'none' }
}
}
}
}
});
const boldState = wordMachine
.transition('bold.off', 'TOGGLE_BOLD')
.value;
// {
// bold: 'on',
// italics: 'off',
// underline: 'off',
// list: 'none'
// }
const nextState = wordMachine
.transition({
bold: 'off',
italics: 'off',
underline: 'on',
list: 'bullets'
}, 'TOGGLE_ITALICS')
.value;
// {
// bold: 'off',
// italics: 'on',
// underline: 'on',
// list: 'bullets'
// }
To provide full flexibility, history states are more arbitrarily defined than the original statechart specification. To go to a history state, use the special key $history
.
const paymentMachine = Machine({
initial: 'method',
states: {
method: {
initial: 'cash',
states: {
cash: { on: { SWITCH_CHECK: 'check' } },
check: { on: { SWITCH_CASH: 'cash' } }
},
on: { NEXT: 'review' }
},
review: {
on: { PREVIOUS: 'method.$history' }
}
}
});
const checkState = paymentMachine
.transition('method.cash', 'SWITCH_CHECK');
// => State {
// value: { method: 'check' },
// history: { $current: { method: 'cash' }, ... }
// }
const reviewState = paymentMachine
.transition(checkState, 'NEXT');
// => State {
// value: 'review',
// history: { $current: { method: 'check' }, ... }
// }
const previousState = paymentMachine
.transition(reviewState, 'PREVIOUS')
.value;
// => { method: 'check' }
More code examples coming soon!
import React, { Component } from 'react'
import { Machine } from 'xstate'
const ROOT_URL = `https://api.github.com/users`
const myMachine = Machine({
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading'
}
},
loading: {
on: {
RESOLVE: 'data',
REJECT: 'error'
}
},
data: {
on: {
CLICK: 'loading'
}
},
error: {
on: {
CLICK: 'loading'
}
}
}
})
class App extends Component {
state = {
data: {},
dataState: 'idle',
input: ''
}
searchRepositories = async () => {
try {
const data = await fetch(`${ROOT_URL}/${this.state.input}`).then(response => response.json())
this.setState(({ data }), this.transition('RESOLVE'))
} catch (error) {
this.transition('REJECT')
}
}
commands = {
loading: this.searchRepositories
}
transition = action => {
const { dataState } = this.state
const newState = myMachine.transition(dataState, action).value
const command = this.commands[newState]
this.setState(
{
dataState: newState
},
command
)
}
render() {
const { data, dataState } = this.state
const buttonText = {
idle: 'Fetch Github',
loading: 'Loading...',
error: 'Github fail. Retry?',
data: 'Fetch Again?'
}[dataState]
return (
<div>
<input
type="text"
value={this.state.input}
onChange={e => this.setState({ input: e.target.value })}
/>
<button
onClick={() => this.transition('CLICK')}
disabled={dataState === 'loading'}
>
{buttonText}
</button>
{data && <div>{JSON.stringify(data, null, 2)}</div>}
{dataState === 'error' && <h1>Error!!!</h1>}
</div>
)
}
}
export default App
FAQs
Finite State Machines and Statecharts for the Modern Web.
The npm package xstate receives a total of 1,285,676 weekly downloads. As such, xstate popularity was classified as popular.
We found that xstate demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
Security News
Members Hub is conducting large-scale campaigns to artificially boost Discord server metrics, undermining community trust and platform integrity.
Security News
NIST has failed to meet its self-imposed deadline of clearing the NVD's backlog by the end of the fiscal year. Meanwhile, CVE's awaiting analysis have increased by 33% since June.