Skip to content

Instantly share code, notes, and snippets.

@jimbol
Created March 27, 2017 21:20
Show Gist options
  • Save jimbol/1b7a33d3e8813b631e5762673aa75bc5 to your computer and use it in GitHub Desktop.
Save jimbol/1b7a33d3e8813b631e5762673aa75bc5 to your computer and use it in GitHub Desktop.
Action-based optimistic reducer enhancer
// ACTION FORMAT
// const scope = 'MY_SCOPE';
// const createAction = (id, payload) => ({
// type: 'START_LOADING',
// payload,
// meta: {
// scope,
// type: EXPECT,
// id,
// },
// });
// REDUCER DEFINITION
// export myOptimisticReducer = makeOptimistic(myReducer, scope);
// NOTES
// `transactions` are actions
// `meta.type` is the optimistic stage we are currently on
// `meta.id` must be the same for an entire request lifecycle
// optional `scope` helps focus actions
export const EXPECT = 'EXPECT';
export const CONFIRM = 'CONFIRM';
export const REJECT = 'REJECT';
export function makeOptimistic(reducer, scope = '') {
const defaultReducerValue = reducer(undefined, {});
const defaultValue = {
confirmed: defaultReducerValue,
transactions: [],
optimistic: defaultReducerValue,
};
return (state = defaultValue, action) => {
const scopedType = (action.meta && action.meta.scope)
? `${action.meta.scope}${action.meta.type}`
: action.meta.type;
switch (scopedType) {
case `${scope}${EXPECT}`: {
const newTransactions = [...state.transactions, action];
const output = {
confirmed: state.confirmed,
transactions: newTransactions,
optimistic: mapActions(
reducer,
state.confirmed,
newTransactions
),
};
return output;
}
case `${scope}${CONFIRM}`: {
const { newTransactions, removedTransaction } = removeTransaction(state, action);
const newConfirmed = mapActions(
reducer,
state.confirmed,
[removedTransaction, action]
);
const output = {
confirmed: newConfirmed,
transactions: newTransactions,
optimistic: mapActions(
reducer,
newConfirmed,
output.transactions
),
};
return output;
}
case `${scope}${REJECT}`: {
const { newTransactions } = removeTransaction(state, action);
const output = {
confirmed: state.confirmed,
transactions: newTransactions,
optimistic: mapActions(
reducer,
state.confirmed,
output.transactions
),
};
return output;
}
default: {
const { confirmed, transactions } = state;
const newConfirmed = reducer(confirmed, action);
if (confirmed === newConfirmed) {
return state;
}
return {
confirmed: newConfirmed,
transactions,
optimistic: mapActions(
reducer,
state.confirmed,
transactions
),
};
}
}
};
}
const mapActions = (reducer, defaultValue, actions) =>
actions.reduce(
(prevOutput, action) => reducer(prevOutput, action),
defaultValue
);
const removeTransaction = (state, action) => {
const { transactions } = state;
const newTransactions = [...transactions];
const id = action.meta.id;
const i = newTransactions.findIndex((t) => t.meta.id === id);
const removedTransaction = newTransactions.splice(i, 1);
return { newTransactions, removedTransaction };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment