Skip to content

Instantly share code, notes, and snippets.

@stojg
Last active October 10, 2016 04:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stojg/26cfc8c3b9085e41ae04000c04bb4a0d to your computer and use it in GitHub Desktop.
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
// 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 = "employee@example.com";
// 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 = 'personal@example.com';
// 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