Skip to content

Instantly share code, notes, and snippets.

@farskid
Last active November 1, 2019 19:06
Show Gist options
  • Save farskid/b791c0bffbf12ac1a8ce49b05a8c7005 to your computer and use it in GitHub Desktop.
Save farskid/b791c0bffbf12ac1a8ce49b05a8c7005 to your computer and use it in GitHub Desktop.
Modeling Netflix login form using statecharts
function validateEmail(email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
function validatePassword(password) {
return password.length >= 5 && password.length < 60;
}
function fakeAsync(succeed) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (succeed) {
resolve();
} else {
reject("Sign in failed");
}
}, 2000);
});
}
const inputStates = {
type: "parallel",
states: {
email: {
on: {
TYPE_EMAIL: [
{
target: "#root.valid",
cond: "allInputsValid"
},
{
actions: send("CHOOSE_INPUT_STATE")
}
],
CHOOSE_INPUT_STATE: [
{
target: "#root.valid",
cond: "allInputsValid"
},
{
target: ".empty",
cond: "isEmailEmpty"
},
{
target: ".valid",
cond: "isEmailValid"
},
{
target: ".invalid"
}
]
},
initial: "empty",
entry: send("CHOOSE_INPUT_STATE"),
states: {
empty: {},
invalid: {},
valid: {}
}
},
password: {
on: {
TYPE_PASSWORD: [
{
target: "#root.valid",
cond: "allInputsValid"
},
{
actions: send("CHOOSE_INPUT_STATE")
}
],
CHOOSE_INPUT_STATE: [
{
target: "#root.valid",
cond: "allInputsValid"
},
{
target: ".empty",
cond: "isPasswordEmpty"
},
{
target: ".valid",
cond: "isPasswordValid"
},
{
target: ".invalid"
}
]
},
initial: "empty",
entry: send("CHOOSE_INPUT_STATE"),
states: {
empty: {},
invalid: {},
valid: {}
}
}
}
};
const inputEvents = {
TYPE: [
{
cond: (_, e) => e.data && e.data.inputKey === "email",
actions: [send("TYPE_EMAIL"), "saveEmail"]
},
{
cond: (_, e) => e.data && e.data.inputKey === "password",
actions: [send("TYPE_PASSWORD"), "savePassword"]
}
]
};
const formMachine = Machine(
{
id: "root",
initial: "invalid",
context: {
email: "",
password: "",
submitError: undefined
},
states: {
invalid: {
on: inputEvents,
...inputStates
},
valid: {
on: {
SUBMIT: "submitting",
...inputEvents
},
type: "parallel",
states: {
email: {
on: {
TYPE_EMAIL: [
{
target: "#root.valid",
cond: "allInputsValid"
},
{
target: "#root.invalid"
}
]
},
initial: "valid",
states: {
valid: {}
}
},
password: {
on: {
TYPE_PASSWORD: [
{
target: "#root.valid",
cond: "allInputsValid"
},
{
target: "#root.invalid"
}
]
},
initial: "valid",
states: {
valid: {}
}
}
}
},
submitting: {
entry: "cleanError",
invoke: {
src: "submitForm",
onDone: "submitSucceeded",
onError: "submitFailed"
}
},
submitSucceeded: {
type: "final"
},
submitFailed: {
entry: "saveError",
on: {
"": "valid"
}
}
}
},
{
services: {
submitForm: () => fakeAsync(Math.random() > 0.5)
},
guards: {
allInputsValid: ctx =>
validateEmail(ctx.email) && validatePassword(ctx.password),
isEmailEmpty: ctx => ctx.email.length === 0,
isEmailValid: ctx => validateEmail(ctx.email),
isPasswordValid: ctx => validatePassword(ctx.password),
isPasswordEmpty: ctx => ctx.password.length === 0
},
actions: {
saveError: assign({
submitError: (_, e) => e.data
}),
cleanError: assign({
submitError: () => undefined
}),
savePassword: assign({
password: (_, e) => e.data && e.data.value
}),
saveEmail: assign({
email: (_, e) => e.data && e.data.value
})
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment