Skip to content

Commit 2cd6b2b

Browse files
committed
Merge pull request reduxjs#856 from aaronjensen/combine-reducers-reference-equality
Have combineReducers return same object if nothing changes
2 parents 408ba41 + 6b8a4a8 commit 2cd6b2b

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed

src/utils/combineReducers.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,16 @@ export default function combineReducers(reducers) {
113113
throw sanityError;
114114
}
115115

116+
var hasChanged = false;
116117
var finalState = mapValues(finalReducers, (reducer, key) => {
117-
var newState = reducer(state[key], action);
118-
if (typeof newState === 'undefined') {
118+
var previousStateForKey = state[key];
119+
var nextStateForKey = reducer(previousStateForKey, action);
120+
if (typeof nextStateForKey === 'undefined') {
119121
var errorMessage = getUndefinedStateErrorMessage(key, action);
120122
throw new Error(errorMessage);
121123
}
122-
return newState;
124+
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
125+
return nextStateForKey;
123126
});
124127

125128
if (process.env.NODE_ENV !== 'production') {
@@ -129,6 +132,6 @@ export default function combineReducers(reducers) {
129132
}
130133
}
131134

132-
return finalState;
135+
return hasChanged ? finalState : state;
133136
};
134137
}

test/utils/combineReducers.spec.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,45 @@ describe('Utils', () => {
112112
expect(reducer({counter: 0}, { type: increment }).counter).toEqual(1);
113113
});
114114

115+
it('should maintain referential equality if the reducers it is combining do', () => {
116+
const reducer = combineReducers({
117+
child1(state = {}) {
118+
return state;
119+
},
120+
child2(state = {}) {
121+
return state;
122+
},
123+
child3(state = {}) {
124+
return state;
125+
}
126+
});
127+
128+
const initialState = reducer(undefined, '@@INIT');
129+
expect(reducer(initialState, { type: 'FOO' })).toBe(initialState);
130+
});
131+
132+
it('should not have referential equality if one of the reducers changes something', () => {
133+
const reducer = combineReducers({
134+
child1(state = {}) {
135+
return state;
136+
},
137+
child2(state = { count: 0 }, action) {
138+
switch (action.type) {
139+
case 'increment':
140+
return { count: state.count + 1 };
141+
default:
142+
return state;
143+
}
144+
},
145+
child3(state = {}) {
146+
return state;
147+
}
148+
});
149+
150+
const initialState = reducer(undefined, '@@INIT');
151+
expect(reducer(initialState, { type: 'increment' })).toNotBe(initialState);
152+
});
153+
115154
it('should throw an error on first call if a reducer attempts to handle a private action', () => {
116155
const reducer = combineReducers({
117156
counter(state, action) {

0 commit comments

Comments
 (0)