From 7b3315e27b6ce92346dee34c6d25e541523db6f9 Mon Sep 17 00:00:00 2001 From: Erik Michaelson Date: Tue, 7 Jun 2016 21:12:30 -0400 Subject: [PATCH] =?UTF-8?q?Add=20optional=20=E2=80=9Coptions=E2=80=9D=20pa?= =?UTF-8?q?ram=20to=20combineReducers=20and=20add=20example=20test=20using?= =?UTF-8?q?=20immutable-js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/combineReducers.js | 27 ++++++++++++++++++++++----- test/combineReducers.spec.js | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2c0803da72..b4981f86f0 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "expect": "^1.8.0", "gitbook-cli": "^0.3.4", "glob": "^6.0.4", + "immutable": "3.8.1", "isparta": "^4.0.0", "mocha": "^2.2.5", "rimraf": "^2.3.4", diff --git a/src/combineReducers.js b/src/combineReducers.js index ad2d48439e..d4fddb1a6b 100644 --- a/src/combineReducers.js +++ b/src/combineReducers.js @@ -87,10 +87,26 @@ function assertReducerSanity(reducers) { * if the state passed to them was undefined, and the current state for any * unrecognized action. * + * @param {Object} [options={ + * create: (obj) => obj, + * get: (state, key) => state[key], + * set: (state, key, value) => { + * state[key] = value + * return state + * } + * }] An optional object for configuring how the top-level state is managed. + * * @returns {Function} A reducer function that invokes every reducer inside the * passed object, and builds a state object with the same shape. */ -export default function combineReducers(reducers) { +export default function combineReducers(reducers, options = { + create: (obj) => obj, + get: (state, key) => state[key], + set: (state, key, stateForKey) => { + state[key] = stateForKey + return state + }, +}) { var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { @@ -99,6 +115,7 @@ export default function combineReducers(reducers) { finalReducers[key] = reducers[key] } } + var finalReducerKeys = Object.keys(finalReducers) var sanityError @@ -108,7 +125,7 @@ export default function combineReducers(reducers) { sanityError = e } - return function combination(state = {}, action) { + return function combination(state = options.create({}), action) { if (sanityError) { throw sanityError } @@ -121,17 +138,17 @@ export default function combineReducers(reducers) { } var hasChanged = false - var nextState = {} + var nextState = options.create({}) for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] - var previousStateForKey = state[key] + var previousStateForKey = options.get(state, key) var nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } - nextState[key] = nextStateForKey + nextState = options.set(nextState, key, nextStateForKey) hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state diff --git a/test/combineReducers.spec.js b/test/combineReducers.spec.js index d861ba371f..c8e83b58ee 100644 --- a/test/combineReducers.spec.js +++ b/test/combineReducers.spec.js @@ -1,4 +1,5 @@ import expect from 'expect' +import Immutable from 'immutable' import { combineReducers } from '../src' import createStore, { ActionTypes } from '../src/createStore' @@ -18,6 +19,27 @@ describe('Utils', () => { expect(s2).toEqual({ counter: 1, stack: [ 'a' ] }) }) + it('accepts options that configure how the combined reducer state is managed', () => { + const reducer = combineReducers({ + counter: (state = 0, action) => + action.type === 'increment' ? state + 1 : state, + stack: (state = [], action) => + action.type === 'push' ? [ ...state, action.value ] : state + }, { + create: (obj) => + (obj instanceof Immutable.Map) ? obj : new Immutable.Map(obj), + get: (state, key) => state.get(key), + set: (state, key, value) => state.set(key, value) + }) + + const s1 = reducer(undefined, { type: 'increment' }) + expect(s1.get('counter')).toEqual(1) + expect(s1.get('stack')).toEqual([]) + const s2 = reducer(s1, { type: 'push', value: 'a' }) + expect(s2.get("counter")).toEqual(1) + expect(s2.get("stack")).toEqual([ 'a' ]) + }) + it('ignores all props which are not a function', () => { const reducer = combineReducers({ fake: true,