Simple, stateless JavaScript finite state machines and statecharts.
Read the slides (video coming soon!) 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
- Pure UI by Guillermo Rauch
- Pure UI Control by Adam Solove
The 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!