Last active
October 10, 2016 04:22
-
-
Save stojg/26cfc8c3b9085e41ae04000c04bb4a0d to your computer and use it in GitHub Desktop.
a mini example on how to implement a state machine for transitioning between states, like a user flow
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// these are generic functions that return boolean depending of the state in data | |
// they are used by state transitions to progress the state machine | |
const hasStarted = (data) => data.started === true; | |
const isEmployee = (data) => data.employee === true; | |
const isPersonal = (data) => data.personal === true; | |
// this is where we set up the full statemachine and connect 'from' states (e.g "initState") and where | |
// they can transition to (e.g. "setTypeState") and what would trigger that transition | |
const transitions = { | |
// initial state | |
'initState': { | |
// transition if the user has clicked the start action | |
'transitions': { 'setTypeState': hasStarted }, | |
// when undo:ing, delete the result from the user clicking the start action | |
'reset' : (data) => { delete(data.started); } | |
}, | |
// check which type of user it is | |
'setTypeState': { | |
'transitions': { | |
// transition to setEmployeeEmailState if isEmployee() returns true | |
'setEmployeeEmailState': isEmployee, | |
// transition to setPersonalEmailState if isPersonal() returns true | |
'setPersonalEmailState': isPersonal | |
}, | |
// on undo, delete the decision the user have done for the 'setTypeState' state | |
'reset': (data) => { | |
delete(data.personal); | |
delete(data.employee); | |
} | |
}, | |
// ask user to fill out email | |
'setEmployeeEmailState': { | |
'transitions': {'setPasswordState': canCreatePassword }, | |
'reset': (data) => { delete(data.employee_email); } | |
}, | |
// ask user to fill out email | |
'setPersonalEmailState': { | |
'transitions': {'setPasswordState': canCreatePassword }, | |
'reset': (data) => { delete(data.personal_email); } | |
} | |
}; | |
// will return the next state or null if it cannot progress further | |
function checkTransition(fromState, data) { | |
if (transitions[fromState] === undefined) { | |
return null; | |
} | |
const checks = Object.keys(transitions[fromState].transitions); | |
// loop through all transitions for this state and check if they triggers. | |
// first transition that returns true will return it's to state. | |
for (let i = 0; i < checks.length; i++) { | |
// call the transition trigger function | |
if (transitions[fromState].transitions[checks[i]](data)) { | |
return checks[i]; | |
} | |
} | |
return null; | |
} | |
// the passed in `path` array will be filled out with a list of states | |
// with the first state in path[0] and the current state in path[path.length-1] | |
function runStateMachine(path, data) { | |
const next = checkTransition(path[path.length - 1], data); | |
if (next) { | |
path.push(next); | |
runStateMachine(path, data); | |
} | |
} | |
// Undo walks "back" one step in the state machine. | |
function undo(path, data) { | |
path.pop() | |
let currentState = path[path.length-1]; | |
if(transitions[currentState].reset) { | |
transitions[currentState].reset(data); | |
} | |
} | |
// set up initial state | |
const path = ['initState']; | |
// this is a 'data bag', e.g the state of the application. It is used for transitions to | |
// decide if they can transition from one state to another. | |
const data = {}; | |
// run the machine for the first time, we should still be in the init state | |
// since the "user" have made an action that sets data.started = true; | |
runStateMachine(path, data); | |
console.log(path, data); | |
// user started the flow | |
data.started = true; | |
// run state machine | |
runStateMachine(path, data); | |
console.log(path, data); | |
// use choose the employee option | |
data.employee = true; | |
// run state machine | |
runStateMachine(path, data); | |
console.log(path, data); | |
// user set her email | |
data.employee_email = "[email protected]"; | |
// run state machine | |
runStateMachine(path, data); | |
console.log(path, data); | |
// user goes back, remembers she isn't an employee | |
undo(path, data); | |
console.log(path, data); | |
// user goes back, remembers she isn't an employee | |
undo(path, data); | |
// now we are back at the stage where we decide personal or employee | |
console.log(path, data); | |
// user chooses personal | |
data.personal = true; | |
// run state machine | |
runStateMachine(path, data); | |
console.log(path, data); | |
// set personal email | |
data.personal_email = '[email protected]'; | |
// run state machine | |
runStateMachine(path, data); | |
// final result [initState, setTypeState, setPersonalEmailState] | |
console.log(path, data); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment