Statecharts

with XState

Content 

  • Problem
  • ​Finite state machine
  • Statecharts
  • XState
  • Profit?
Because everybody hates a stuck loading effect
fetch
result
loading
error
clickclickclick
other states/events

Finite state machines

Finite number of states
Finite number of events
An initial state
Transition function ( state + event = nextState)

Finite state machine

Statecharts

Same as finite state machine...
Just a bit more

Statecharts

Actions

Guards

Hierarchy

Luckily we have XState

  • ​works with JS/TS
  • framework and platform independent
  • works with or replaces vuex/redux

XState + Vue + Vuex

<div>{{ this.$store.state.currentState }}</div> const lightMachine = xstate.Machine({ key: 'light', initial: 'green', states: { green: { on: { TIMER: 'yellow' } }, yellow: { on: { TIMER: 'red' } }, red: { on: { TIMER: 'green' } } } }); this.$store.commit('setCurrentState', lightMachine.transition(state.currentState, 'TIMER').value);

Actions

const switchMachine = Machine( { id: 'alwaysOff', initial: 'on', states: { on_state: { on: { TOGGLE: { target: 'off_state', entry: ['notifyTurnedOn'], actions: send('TOGGLE'), exit: ['notifyTurnedOff'] } } }, off_state: { on: { TOGGLE: 'on_state' } } } }, { actions: { notifyTurnedOn: (context, event) => { ... } notifyTurnedOff: (context, event) => { ... } } } );

Guards

const searchMachine = Machine( { states: { idle: { on: { SEARCH: { target: 'search_inprogress', cond: 'searchValid' } } } // ... } }, { guards: { searchValid: (context, event) => { return context.canSearch && event.query && event.query.length > 0; } } } );

Multiple Guards

states: { login: { on: { LOGIN_RESPONSE: [ { target: 'admin_page', cond: 'hasAdminRights' }, { target: 'welcome_page', cond: 'loginSuccessful' }, { target: 'login_error' } ] } }, // ... }

Delayed event/transitions

{ states: { green: { after: { 1000: { target: 'yellow' } } }, yellow: { after: { YELLOW_DELAY: { target: 'red' } } } } }, { delays: { YELLOW_DELAY: (context, event) => { return context.trafficLevel === 'low' ? 1000 : 3000; } } }

Hierarchy

const wordMachine = Machine({ id: 'word', type: 'parallel', 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' } } } } } });

Hierarchy

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' // }

More easily

import { Machine, interpret } from 'xstate'; const machine = Machine(/* machine config */); const service = interpret(machine); service.start(); service.send('TOGGLE_BOLD'); service.send('TOGGLE_ITALICS'); service.stop();
const tapePlayerMachine = Machine( { id: 'tape player', initial: 'stopped', context: { position: 0 }, states: { forwarding: { onEntry: ['forwardingEffect'], after: { 500: [ { target: 'forwarding', cond: ctx => ctx.position < 15 }, { target: 'stopped' } ] }, on: { PLAY: 'playing', STOP: 'stopped' } }, rewinding: { onEntry: ['rewindingEffect'], after: { 500: [ { target: 'rewinding', cond: ctx => ctx.position > 0 }, { target: 'stopped' } ] }, on: { STOP: 'stopped' } }, stopped: { on: { PLAY: { target: 'playing' }, FORWARD: 'forwarding', REWIND: 'rewinding' } }, playing: { onEntry: ['playingEffect'], after: { 500: [ { target: 'playing', cond: ctx => ctx.position < 15 }, { target: 'stopped' } ] }, on: { FORWARD: 'forwarding', STOP: 'stopped' } } } }, { actions: { playingEffect: (context, event) => context.position++, forwardingEffect: (context, event) => context.position, rewindingEffect: (context, event) => context.position-- } } );

Let's get visual

But there is many more...

Checkout xstate.js.org

Mikor vennél részt guild előadáson szívesebben

Reggeli után

Ebéd előtt

Ebéd után

Délután

Go to deckdeckgo.com/poll and use the code {0}
Awaiting votes