Skip to content

Instantly share code, notes, and snippets.

@zushenyan
Created February 11, 2020 01:05
Show Gist options
  • Save zushenyan/369fe4b48260d8ad3d7bcaca1e303ab4 to your computer and use it in GitHub Desktop.
Save zushenyan/369fe4b48260d8ad3d7bcaca1e303ab4 to your computer and use it in GitHub Desktop.
xstate
import { Machine, Interpreter, assign, spawn } from 'xstate'
import InputMachine, {
Context as InputContext,
Schema as InputSchema,
Events as InputEvents
} from './inputMachine'
import { submitForm, Response } from './api'
export type Context = {
email: Interpreter<InputContext, InputSchema, InputEvents> | undefined
password: Interpreter<InputContext, InputSchema, InputEvents> | undefined
error: string
}
export type Schema = {
states: {
initializing: {},
idle: {
states: {
ready: {},
error: {
states: {
client: {},
server: {}
}
}
}
},
submitting: {
states: {
clientValidation: {},
serverValidation: {}
}
},
submitted: {}
}
}
type SubmitFormEvent = {
type: 'error.platform.submitForm'
data: Response
}
export type Events =
{ type: 'SUBMIT' }
| InputEvents
| SubmitFormEvent
const spawnFields = assign<Context, Events>({
email: () => spawn(InputMachine, { name: 'email' }),
password: () => spawn(InputMachine, { name: 'password' }),
})
const enableFields = (ctx: Context) => {
ctx?.email?.send('IDLE')
ctx?.password?.send('IDLE')
}
const disableFields = (ctx: Context) => {
ctx?.email?.send('DISABLE')
ctx?.password?.send('DISABLE')
}
const validateFields = (ctx: Context) => {
ctx?.email?.send('VALIDATE')
ctx?.password?.send('VALIDATE')
}
const invalidFields = (ctx: Context) => {
return ctx?.email?.state.matches('validation.error')
|| ctx?.password?.state.matches('validation.error')
|| false
}
const setError = assign<Context, Events>({
error: (ctx, event) => (event as SubmitFormEvent).data.message
})
const submitFormService = (ctx: Context) => {
const email = ctx?.email?.state.context.value ?? ''
const password = ctx?.password?.state.context.value ?? ''
return submitForm({ email, password })
}
export default Machine<Context, Schema, Events>(
{
initial: 'initializing',
id: 'root',
context: {
email: undefined,
password: undefined,
error: '',
},
states: {
initializing: {
entry: ['spawnFields'],
on: {
'': 'idle.ready'
}
},
idle: {
entry: ['enableFields'],
on: {
SUBMIT: {
target: 'submitting.clientValidation',
actions: ['disableFields', 'validateFields']
},
},
initial: 'ready',
states: {
ready: {},
error: {
states: {
client: {},
server: {}
}
},
}
},
submitting: {
initial: 'clientValidation',
states: {
clientValidation: {
after: {
100: [
{ target: '#root.idle.error.client', cond: 'invalidFields' },
{ target: 'serverValidation' }
],
}
},
serverValidation: {
invoke: {
id: 'submitForm',
src: 'submitFormService',
onDone: '#root.submitted',
onError: {
target: '#root.idle.error.server',
actions: 'setError'
}
}
}
}
},
submitted: {
type: 'final'
}
}
},
{
actions: {
spawnFields,
disableFields,
validateFields,
enableFields,
setError,
},
guards: {
invalidFields,
},
services: {
submitFormService
}
}
)
import { Machine, assign, send } from 'xstate'
export type Context = {
value: string,
}
export type Schema = {
states: {
input: {
states: {
idle: {},
disabled: {},
focused: {},
blurred: {},
}
},
validation: {
states: {
good: {},
error: {
states: {
empty: {},
tooLong: {},
}
}
}
}
}
}
export type Events =
{ type: 'FOCUS' }
| { type: 'DISABLE' }
| { type: 'IDLE' }
| { type: 'CHANGE', value: string }
| { type: 'BLUR' }
| { type: 'VALIDATE' }
const onChange = assign<Context, Events>({
value: (ctx, e) => {
if (e.type === 'CHANGE') return e.value
return ctx.value
}
})
const empty = (ctx: Context) => ctx.value.length === 0
const tooLong = (ctx: Context) => ctx.value.length > 6
export default Machine<Context, Schema, Events>(
{
context: {
value: '',
},
on: {
VALIDATE: [
{ target: 'validation.error.empty', cond: 'empty' },
{ target: 'validation.error.tooLong', cond: 'tooLong' },
{ target: 'validation.good' },
],
FOCUS: 'input.focused',
DISABLE: 'input.disabled',
BLUR: 'input.blurred'
},
type: 'parallel',
states: {
input: {
initial: 'idle',
states: {
idle: {},
disabled: {
on: {
IDLE: 'idle'
}
},
focused: {
on: {
CHANGE: {
target: 'focused',
actions: ['onChange']
},
},
},
blurred: {
entry: send('VALIDATE'),
}
}
},
validation: {
initial: 'good',
states: {
good: {},
error: {
states: {
empty: {},
tooLong: {},
}
},
}
}
}
},
{
actions: {
onChange,
},
guards: {
empty,
tooLong
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment