Skip to content

Instantly share code, notes, and snippets.

@bultas
Last active January 22, 2020 19:49
Show Gist options
  • Save bultas/7015d7f2f26206feee2620ed5d0afdb9 to your computer and use it in GitHub Desktop.
Save bultas/7015d7f2f26206feee2620ed5d0afdb9 to your computer and use it in GitHub Desktop.
react reducer/state reducer/state machine solution comparision
import React, { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'removeEntry': {
const newEntries = remove(action.payload, 1, state.entries);
return { ...state, entries: newEntries, editIndex: null };
}
case 'saveEntry': {
return {
...state,
entries: update(action.payload.index, action.payload.entry, state.entries),
editIndex: null
};
}
case 'editEntry': {
return {
...state,
editIndex: action.payload,
newEntry: false
};
}
case 'cancelEntry': {
return {
...state,
editIndex: null
};
}
case 'addEntry': {
const newEntries = addImport(action.payload)(state.entries);
return {
...state,
entries: newEntries,
newEntry: false
};
}
case 'newEntry': {
return {
...state,
newEntry: true,
editIndex: null
};
}
case 'cancelNewEntry': {
return {
...state,
newEntry: false
};
}
default:
throw new Error();
}
};
export const ImportMerge = ({ entries }) => {
const [state, dispatch] = useReducer(reducer, { entries, editIndex: null, newEntry: false });
return (
<>
{state.entries.map((entry, index) => {
const key = `${JSON.stringify(entry)}`;
if (state.editIndex === index) {
return (
<div key={key}>
<EntryEditor
onSave={newEntry => {
dispatch({ type: 'saveEntry', payload: { entry: newEntry, index } });
}}
entry={entry}
/>
<Button form={FORM_ID} type="submit">
Save
</Button>
<Button
onClick={() => {
dispatch({ type: 'cancelEntry' });
}}
>
Cancel
</Button>
<Button
onClick={() => {
dispatch({ type: 'removeEntry', payload: index });
}}
>
Remove
</Button>
</div>
);
}
return (
<EditableContent
key={key}
onClick={() => {
dispatch({ type: 'editEntry', payload: index });
}}
>
<EntryViewer {...entry} />
</EditableContent>
);
})}
{!state.newEntry && isNil(state.editIndex) && (
<Button
onClick={() => {
dispatch({ type: 'newEntry' });
}}
>
Add new
</Button>
)}
{state.newEntry && (
<>
<EntryEditor
onSave={newEntry => {
dispatch({ type: 'addEntry', payload: newEntry });
}}
/>
<Button form={FORM_ID} type="submit">
Add
</Button>
<Button
onClick={() => {
dispatch({ type: 'cancelNewEntry' });
}}
>
Cancel
</Button>
</>
)}
</>
);
};
import React from 'react';
import { Machine, assign } from 'xstate';
import { useMachine } from '@xstate/react';
const editAction = assign({
editIndex: (context, { payload: editIndex }) => editIndex
});
const onEdit = {
target: 'editing',
actions: editAction
};
const createImportMergeMachine = ({ entries = [], editIndex = null }) =>
Machine({
id: 'importMachine',
initial: 'listing',
context: {
entries,
editIndex
},
states: {
listing: {
on: {
add: 'adding',
edit: onEdit
}
},
adding: {
on: {
save: {
target: 'listing',
actions: assign({
entries: ({ entries }, { payload }) => addImport(payload)(entries)
})
},
edit: onEdit,
cancel: 'listing'
}
},
editing: {
on: {
cancel: 'listing',
edit: onEdit,
save: {
target: 'listing',
actions: assign({
entries: ({ entries, editIndex }, { payload }) => update(editIndex)(payload)(entries)
})
},
remove: {
target: 'listing',
actions: assign({
entries: ({ entries, editIndex }) => remove(editIndex, 1, entries)
})
}
}
}
}
});
const saveEventCreator = send => entry => send({ type: 'save', payload: entry });
const cancelEventCreator = send => () => send('cancel');
export const ImportMerge = ({ entries }) => {
const [current, send] = useMachine(createImportMergeMachine({ entries }));
const { entries: currentEntries, editIndex } = current.context;
const status = current.value;
const cancelEvent = cancelEventCreator(send);
const saveEvent = saveEventCreator(send);
return (
<>
{currentEntries.map((entry, index) => {
const key = `${JSON.stringify(entry)}`;
if (status === 'editing' && editIndex === index) {
return (
<div key={key}>
<EntryEditor onSave={saveEvent} entry={entry} />
<Button form={FORM_ID} type="submit">
Save
</Button>
<Button onClick={cancelEvent}>Cancel</Button>
<Button onClick={() => send('remove')}>Remove</Button>
</div>
);
}
return (
<EditableContent
key={key}
onClick={() =>
send({
type: 'edit',
payload: index
})
}
>
<EntryViewer {...entry} />
</EditableContent>
);
})}
{status === 'listing' && <Button onClick={() => send('add')}>Add new</Button>}
{status === 'adding' && (
<>
<EntryEditor onSave={saveEvent} />
<Button form={FORM_ID} type="submit">
Add
</Button>
<Button onClick={cancelEvent}>Cancel</Button>
</>
)}
</>
);
};
import React, { useReducer } from 'react';
const reducer = (state, event) => {
switch (event.type) {
case 'removeEntry':
return {
...state,
status: 'listing',
entries: remove(event.payload, 1, state.entries)
};
case 'saveEntry':
return {
...state,
status: 'listing',
entries: update(event.payload.index, event.payload.entry, state.entries)
};
case 'editEntry':
return {
...state,
status: 'editing',
editIndex: event.payload
};
case 'cancelEntry':
return {
...state,
status: 'listing'
};
case 'addEntry':
return {
...state,
status: 'listing',
entries: addImport(event.payload)(state.entries)
};
case 'newEntry':
return {
...state,
status: 'adding'
};
case 'cancelNewEntry':
return {
...state,
status: 'listing'
};
default:
throw new Error();
}
};
export const ImportMerge = ({ entries }) => {
const [state, dispatch] = useReducer(reducer, {
status: 'listing',
entries,
editIndex: null
});
const { status, editIndex, entries: currentEntries } = state;
return (
<>
{currentEntries.map((entry, index) => {
const key = `${JSON.stringify(entry)}`;
if (status === 'editing' && editIndex === index) {
return (
<div key={key}>
<EntryEditor
onSave={newEntry => {
dispatch({ type: 'saveEntry', payload: { entry: newEntry, index } });
}}
entry={entry}
/>
<Button form={FORM_ID} type="submit">
Save
</Button>
<Button
onClick={() => {
dispatch({ type: 'cancelEntry' });
}}
>
Cancel
</Button>
<Button
onClick={() => {
dispatch({ type: 'removeEntry', payload: index });
}}
>
Remove
</Button>
</div>
);
}
return (
<EditableContent
key={key}
onClick={() => {
dispatch({ type: 'editEntry', payload: index });
}}
>
<EntryViewer {...entry} />
</EditableContent>
);
})}
{status === 'listing' && (
<Button
onClick={() => {
dispatch({ type: 'newEntry' });
}}
>
Add new
</Button>
)}
{status === 'adding' && (
<>
<EntryEditor
onSave={newEntry => {
dispatch({ type: 'addEntry', payload: newEntry });
}}
/>
<Button form={FORM_ID} type="submit">
Add
</Button>
<Button
onClick={() => {
dispatch({ type: 'cancelNewEntry' });
}}
>
Cancel
</Button>
</>
)}
</>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment