Skip to content

Instantly share code, notes, and snippets.

@vin-e
Created July 12, 2020 06:14
Show Gist options
  • Save vin-e/9d2e59c6d3113929fc50bfb9fe44ef6b to your computer and use it in GitHub Desktop.
Save vin-e/9d2e59c6d3113929fc50bfb9fe44ef6b to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
const debugDelay = 2500
const FormMachine = Machine({
id: 'form',
initial: 'idle',
context: {
testId: 34,
schema: undefined,
entity: undefined,
errorMessage: undefined,
values: {}
},
states: {
idle: {
on: {
'': [{
cond: 'isExistingTestId',
target: 'loadEntity'
}, {
target: 'loadSchema',
actions: 'defineDefaultEntity'
}]
}
},
loadEntity: {
invoke: {
src: 'getData',
onDone: {
target: 'loadSchema',
actions: 'saveEntity'
},
onError: 'failure.badEntity'
}
},
loadSchema: {
invoke: {
src: 'getSchema',
onDone: {
target: 'editing',
actions: [
'transformEntity'
]
},
onError: 'failure.schemaNotFound'
}
},
editing: {
initial: 'idle',
states: {
idle: {
on: {
'': [{
cond: 'isDataPristine',
target: 'pristine'
}, {
target: 'dirty'
}]
}
},
pristine: {},
success: {},
dirty: {},
error: {}
},
on: {
CHANGE_TEST_TYPE: {
cond: 'hasTestTypeDefined',
target: '#form.loadSchema',
actions: 'setTestType'
},
UPDATE_FIELD: {
target: '.idle',
actions: 'setFieldValue'
},
SUBMIT: 'submit'
}
},
submit: {
initial: 'validate',
states: {
validate: {
on: {
'': [{
cond: 'validate',
target: 'submitting'
}, {
target: '#form.editing.error',
actions: 'setValidationErrorMessage'
}]
}
},
submitting: {
invoke: {
src: 'submitData',
onDone: {
target: '#form.editing.success'
},
onError: {
target: '#form.editing.error',
actions: 'setSaveErrorMessage'
}
}
}
}
},
failure: {
initial: 'unknown',
states: {
unknown: {},
badEntity: {},
schemaNotFound: {}
}
}
}
}, {
guards: {
hasTestTypeDefined: (context, event) => !!event.test_type && context.values.test_types.values !== event.test_type,
isDataPristine: (context) => compareEntityAndValues(context),
isExistingTestId: (context, _event) => context.testId >= 0,
validate: () => true
},
actions: {
saveEntity: assign((_context, event) => ({
entity: event.data
})),
setFieldValue: assign((context, event) => ({
values: {
...context.values,
[event.fieldName]: {
...context.values[event.fieldName],
values: event.values
}
}
})),
setSaveErrorMessage: assign({
errorMessage: 'error saving data'
}),
setTestType: assign((context, event) => ({
values: {
...context.values,
test_type: {
...context.values.test_type,
values: event.test_type
}
}
})),
setValidationErrorMessage: assign({
errorMessage: 'validation error'
}),
transformEntity: assign((context, event) => ({
schema: event.data,
values: transformer(context, event)
}))
},
services: {
getData: () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
testId: 123,
test_type: 'record_duplicates',
name: 'This does work!'
})
}, debugDelay)
}),
getSchema: (context, _event) => new Promise((resolve, reject) => {
const test_type = context.values && context.values.test_type
? context.values.test_type.values
: (context.entity ? context.entity.test_type : undefined)
if (!test_type) {
reject()
}
setTimeout(() => {
resolve({
type: 'object',
properties: {
testId: { type: 'number' },
name: { type: 'string' },
test_type: {
type: 'string',
default: test_type
}
},
required: ['name', 'test_type']
})
}, debugDelay)
}),
submitData: () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, debugDelay)
})
}
});
const convertToSaveEntity = (context) => {
if (!context.values) {
return
}
return Object.keys(context.values).reduce((acc, value) => {
if (context.values[value].enabled) {
acc[value] = context.values[value].values
}
return acc
}, {})
}
const compareEntityAndValues = (context) => {
const currentValues = convertToSaveEntity(context)
if (!context.entity || !currentValues) {
return false
}
// Maybe switch to ramda or lodash/fp to compare objects.
return JSON.stringify(currentValues, Object.keys(currentValues).sort()) === JSON.stringify(context.entity, Object.keys(context.entity).sort())
}
const transformer = (context, event) => {
const disabledFields = context.values === undefined
? undefined
: Object.keys(context.values).reduce((acc, key) => {
if (context.values[key].enabled && event.data.properties[key] === undefined) {
acc[key] = {
...context.values[key],
enabled: false
}
}
return acc
}, {})
const fieldProperties = Object.keys(event.data.properties)
const enabledFields = fieldProperties.reduce((acc, prop) => {
const fieldContextValue = !!context.values && !!context.values[prop] && context.values[prop].values
const entityValue = !fieldContextValue && !!context.entity && context.entity[prop]
// TODO: set testId and testType
acc[prop] = {
enabled: true,
validator: () => console.log(`Validator for ${prop} from ajv is required to complete this.`),
values: fieldContextValue || entityValue || event.data.properties[prop].default
}
return acc
}, {})
return {
...disabledFields,
...enabledFields
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment