Functional, stateless JavaScript finite state machines and statecharts.
In short, 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.
Read ๐ฝ the slides (๐ฅ video) or check out these resources for learning about the importance of finite state machines and statecharts in user interfaces:
- Statecharts - A Visual Formalism for Complex Systems by David Harel
- The World of Statecharts by Erik Mogensen
- Pure UI by Guillermo Rauch
- Pure UI Control by Adam Solove
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')
.value;
// => {
// red: 'walk'
// }
lightMachine
.transition('red.walk', 'PED_TIMER')
.value;
// => {
// 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: State { ... }
// }
const reviewState = paymentMachine
.transition(checkState, 'NEXT');
// => State {
// value: 'review',
// history: State { ... }
// }
const previousState = paymentMachine
.transition(reviewState, 'PREVIOUS')
.value;
// => { method: 'check' }