Skip to content

Instantly share code, notes, and snippets.

@martypenner
Created November 29, 2019 21:01
Show Gist options
  • Save martypenner/9499edea003c0e4a07cab79457236c0e to your computer and use it in GitHub Desktop.
Save martypenner/9499edea003c0e4a07cab79457236c0e to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
const processItem = ({ subEvents: appliesToSubEvents, fromDate, toDate, ...item }) => ({
...item,
appliesToSubEvents: appliesToSubEvents || [],
fromDate: fromDate == null || (typeof fromDate === 'string' && fromDate.trim() === '') ? null : moment(fromDate),
toDate: toDate == null || (typeof toDate === 'string' && toDate.trim() === '') ? null : moment(toDate),
});
const lineItemsMachine = Machine(
{
strict: true,
id: 'lineItems',
context: {
items: [],
itemIdsOnBackEnd: [],
removedItemsThatExistOnBackEnd: [],
itemToRemove: null,
maxId: 0,
snacks: [],
},
type: 'parallel',
states: {
items: {
id: 'items',
initial: 'initializing',
states: {
initializing: {
entry: assign({
items: (ctx) =>
ctx.items.map((item, index) => {
const newItem = processItem({
...lineItemMachine.context,
...item,
existsOnBackEnd: true,
});
return {
...newItem,
// Reset display order to match array order. This is
// defensive.
displayOrder: index,
ref: spawn(lineItemMachine.withContext(newItem), `line-item-${item.id}`),
};
}),
}),
on: {
// Transition immediately after settings things up
'': 'unknown',
},
},
// Unknown has been modeled as a separate state so we can transition
// directly to it instead of revisiting initializing when an item is
// removed
unknown: {
on: {
'': [
{
target: 'hasItems',
cond: 'hasItems',
},
{ target: 'empty' },
],
},
},
hasItems: {},
empty: {},
},
},
confirmation: {
initial: 'hidden',
states: {
hidden: {},
visible: {
on: {
CONFIRMED: {
target: ['hidden', '#items.unknown'],
actions: 'deleteItem',
},
CANCELLED: {
target: ['hidden', '#items.unknown'],
},
},
},
},
},
saving: {
initial: 'idle',
states: {
idle: {
on: {
SAVED_ITEMS: 'saving.inPage',
SAVED_ITEMS_AND_EXITED: 'saving.withExit',
},
},
saving: {
initial: 'inPage',
states: {
inPage: {
invoke: {
src: 'saveItems',
onError: {
target: '#failure',
actions: 'makeSnacks',
},
},
},
withExit: {
invoke: {
src: 'saveItemsWithExit',
onError: {
target: '#failure',
actions: 'makeSnacks',
},
},
},
},
},
failure: {
id: 'failure',
on: {
SAVED_ITEMS: 'saving.inPage',
SAVED_ITEMS_AND_EXITED: 'saving.withExit',
},
},
},
},
},
// In each of these events, we have to ensure that we transition to
// `items.unknown` explicitly. Otherwise, for SOME reason, that state thinks
// it needs to re-enter `initializing`, spawning new actors for each item.
// Clustering all unrelated states under a `ready` state and moving the
// `initializing` state up a level doesn't resolve the problem.
on: {
ADDED_ITEM: {
target: 'items.unknown',
actions: 'createItem',
},
CHANGED_ITEM: {
target: 'items.unknown',
actions: 'updateItem',
},
REMOVED_ITEM: {
target: ['confirmation.visible', 'items.unknown'],
actions: 'storeItemToRemove',
},
EXPANDED_ALL: {
target: 'items.unknown',
actions: 'expandAll',
},
COLLAPSED_ALL: {
target: 'items.unknown',
actions: 'collapseAll',
},
DROPPED_ITEM: {
target: 'items.unknown',
actions: 'updateDisplayOrder',
},
SNACK_DISMISSED: {
target: 'items.unknown',
actions: 'dismissSnack',
},
},
},
{
guards: {
hasItems: (ctx) => ctx.items.length > 0,
},
},
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment