Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save evocateur/d50acd5d9b1472b69eab1665d17e731d to your computer and use it in GitHub Desktop.
Save evocateur/d50acd5d9b1472b69eab1665d17e731d 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