Skip to content

Instantly share code, notes, and snippets.

@elado
Last active March 24, 2019 17:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save elado/95484b754f31fcd6846c7e75de4aafe4 to your computer and use it in GitHub Desktop.
Save elado/95484b754f31fcd6846c7e75de4aafe4 to your computer and use it in GitHub Desktop.
Redux Indexed List Reducer Generator
import shallowequal from 'shallowequal'
import _ from 'lodash'
export const LIST_UPSERT = '@@list/LIST_UPSERT'
export const LIST_DELETE = '@@list/LIST_DELETE'
const ids = (state=[], action) => {
switch (action.type) {
case LIST_UPSERT: {
const hasAt = typeof action.at !== 'undefined'
const includesItem = state.includes(action.id)
if (includesItem && !hasAt) return state
if (hasAt) {
state = state.slice()
if (includesItem) _.pull(state, action.id)
state.splice(action.at, 0, action.id)
return state
}
else {
return [ ...state, action.id ]
}
}
case LIST_DELETE:
if (!state.includes(action.id)) return state
return _.without(state, action.id)
default:
return state
}
}
function byIdReducerGenerator(itemReducer, initialState={}) {
return (state={}, action) => {
switch (action.type) {
case LIST_UPSERT: {
const newItem = itemReducer(state[action.id], action.innerAction)
if (state[action.id] && shallowequal(state[action.id], newItem)) return state
return {
...state,
[action.id]: newItem
}
}
case LIST_DELETE: {
if (!(action.id in state)) return state
return _.omit(state, action.id)
}
default:
return state
}
}
}
export default function indexedListReducerGenerator(itemReducer, initialState={ byId: {}, ids: [] }) {
const byId = byIdReducerGenerator(itemReducer, initialState.byId)
return function (state=initialState, action) {
switch (action.type) {
case LIST_UPSERT:
case LIST_DELETE: {
const newById = byId(state.byId, action)
if (newById === state.byId) return state
const newIds = ids(state.ids, action)
return {
...state,
ids: newIds,
byId: newById
}
}
default:
return state
}
}
}
import { createReducer } from 'redux-create-reducer'
import * as types from '../constants/ActionTypes'
import indexedListReducerGenerator, { LIST_UPSERT, LIST_DELETE } from './indexedListReducerGenerator'
const initialState = {
items: { byId: {}, ids: [] }
}
const quantityCounter = createReducer(0, {
[types.CART_ADD_ITEM](state, action) { return 1 },
[types.CART_REMOVE_ITEM](state, action) { return 0 },
[types.CART_INC_ITEM_QUANTITY](state, action) { return state + 1 },
[types.CART_DEC_ITEM_QUANTITY](state, action) { return Math.max(state - 1, 0) },
})
function cartEntry(state={ quantity: 0 }, action) {
switch (action.type) {
case types.CART_ADD_ITEM:
case types.CART_REMOVE_ITEM:
case types.CART_INC_ITEM_QUANTITY:
case types.CART_DEC_ITEM_QUANTITY:
return {
...state,
quantity: quantityCounter(state.quantity, action)
}
default:
return state
}
}
const itemsByIdReducer = indexedListReducerGenerator(cartEntry)
export default function cartReducer(state=initialState, action) {
let listActionType
switch (action.type) {
case types.CART_ADD_ITEM:
case types.CART_INC_ITEM_QUANTITY:
case types.CART_DEC_ITEM_QUANTITY:
listActionType = LIST_UPSERT
break
case types.CART_REMOVE_ITEM:
listActionType = LIST_DELETE
break
default:
return state
}
return {
...state,
items: itemsByIdReducer(state.items, { type: listActionType, id: action.item.id, innerAction: action } ),
}
}
import * as types from '../constants/ActionTypes'
export const addItem = (item) => {
return { type: types.CART_ADD_ITEM, item }
}
export const removeItem = (item) => {
return { type: types.CART_REMOVE_ITEM, item }
}
export const incQuantity = (item) => {
return { type: types.CART_INC_ITEM_QUANTITY, item }
}
export const decQuantity = (item) => {
return { type: types.CART_DEC_ITEM_QUANTITY, item }
}
import { createSelector } from 'reselect'
const cartItemsSelector = createSelector(
state => state.cart.items.ids,
state => state.cart.items.byId,
state => state.items.byId, // this is another map of item entries by ID
(cartItemIds, cartItemsById, itemsById) => cartItemIds.map(id => ({
...cartItemsById[id],
...itemsById[id]
}))
)
function mapStateToProps(state) {
return {
items: cartItemsSelector(state)
}
}
// in render(), this.props.items is an array of items merged with the cart entry properties (currently only 'quantity')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment