Skip to content

Commit c120054

Browse files
committed
Merge pull request reduxjs#720 from ellbee/always-call-verifyStateShape
Always call verifyStateShape for combined reducers
2 parents 5bd8267 + 6fff5e3 commit c120054

File tree

2 files changed

+43
-49
lines changed

2 files changed

+43
-49
lines changed

src/utils/combineReducers.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ function getErrorMessage(key, action) {
1515
);
1616
}
1717

18-
function verifyStateShape(initialState, currentState) {
19-
var reducerKeys = Object.keys(currentState);
18+
function verifyStateShape(inputState, outputState, action) {
19+
var reducerKeys = Object.keys(outputState);
20+
var argumentName = action && action.type === ActionTypes.INIT ?
21+
'initialState argument passed to createStore' :
22+
'previous state received by the reducer';
2023

2124
if (reducerKeys.length === 0) {
2225
console.error(
@@ -26,25 +29,26 @@ function verifyStateShape(initialState, currentState) {
2629
return;
2730
}
2831

29-
if (!isPlainObject(initialState)) {
32+
if (!isPlainObject(inputState)) {
3033
console.error(
31-
'initialState has unexpected type of "' +
32-
({}).toString.call(initialState).match(/\s([a-z|A-Z]+)/)[1] +
33-
'". Expected initialState to be an object with the following ' +
34+
`The ${argumentName} has unexpected type of "` +
35+
({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
36+
`". Expected argument to be an object with the following ` +
3437
`keys: "${reducerKeys.join('", "')}"`
3538
);
3639
return;
3740
}
3841

39-
var unexpectedKeys = Object.keys(initialState).filter(
42+
var unexpectedKeys = Object.keys(inputState).filter(
4043
key => reducerKeys.indexOf(key) < 0
4144
);
4245

4346
if (unexpectedKeys.length > 0) {
4447
console.error(
4548
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
46-
`"${unexpectedKeys.join('", "')}" in initialState will be ignored. ` +
47-
`Expected to find one of the known reducer keys instead: "${reducerKeys.join('", "')}"`
49+
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
50+
`Expected to find one of the known reducer keys instead: ` +
51+
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
4852
);
4953
}
5054
}
@@ -94,7 +98,6 @@ export default function combineReducers(reducers) {
9498
});
9599

96100
var defaultState = mapValues(finalReducers, () => undefined);
97-
var stateShapeVerified;
98101

99102
return function combination(state = defaultState, action) {
100103
var finalState = mapValues(finalReducers, (reducer, key) => {
@@ -106,10 +109,7 @@ export default function combineReducers(reducers) {
106109
});
107110

108111
if (process.env.NODE_ENV !== 'production') {
109-
if (!stateShapeVerified) {
110-
verifyStateShape(state, finalState);
111-
stateShapeVerified = true;
112-
}
112+
verifyStateShape(state, finalState, action);
113113
}
114114

115115
return finalState;

test/utils/combineReducers.spec.js

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import expect from 'expect';
22
import { combineReducers } from '../../src';
3-
import { ActionTypes } from '../../src/createStore';
3+
import createStore, { ActionTypes } from '../../src/createStore';
44

55
describe('Utils', () => {
66
describe('combineReducers', () => {
@@ -130,61 +130,55 @@ describe('Utils', () => {
130130
spy.restore();
131131
});
132132

133-
it('should warn if initial state object does not match state object returned by reducer', () => {
133+
it('should warn if input state object does not match state object returned by reducer', () => {
134134
const spy = expect.spyOn(console, 'error');
135-
const reducerCreator = () => {
136-
return combineReducers({
137-
foo(state = {bar: 1}) {
138-
return state;
139-
},
140-
baz(state = {qux: 3}) {
141-
return state;
142-
}
143-
});
144-
};
135+
const reducer = combineReducers({
136+
foo(state = {bar: 1}) {
137+
return state;
138+
},
139+
baz(state = {qux: 3}) {
140+
return state;
141+
}
142+
});
145143

146-
reducerCreator()({foo: {bar: 2}});
144+
reducer({foo: {bar: 2}});
147145
expect(spy.calls.length).toBe(0);
148146

149-
reducerCreator()({
147+
reducer({
150148
foo: {bar: 2},
151149
baz: {qux: 4}
152150
});
153151
expect(spy.calls.length).toBe(0);
154152

155-
reducerCreator()({bar: 2});
153+
createStore(reducer, {bar: 2});
156154
expect(spy.calls[0].arguments[0]).toMatch(
157-
/Unexpected key "bar".*instead: "foo", "baz"/
155+
/Unexpected key "bar".*createStore.*instead: "foo", "baz"/
158156
);
159157

160-
reducerCreator()({bar: 2, qux: 4});
158+
createStore(reducer, {bar: 2, qux: 4});
161159
expect(spy.calls[1].arguments[0]).toMatch(
162-
/Unexpected keys "bar", "qux".*instead: "foo", "baz"/
160+
/Unexpected keys "bar", "qux".*createStore.*instead: "foo", "baz"/
163161
);
164162

165-
reducerCreator()(1);
163+
createStore(reducer, 1);
166164
expect(spy.calls[2].arguments[0]).toMatch(
167-
/unexpected type of "Number".*keys: "foo", "baz"/
165+
/createStore has unexpected type of "Number".*keys: "foo", "baz"/
168166
);
169167

170-
spy.restore();
171-
});
172-
173-
it('should only check state shape on init', () => {
174-
const spy = expect.spyOn(console, 'error');
175-
const reducer = combineReducers({
176-
foo(state = {bar: 1}) {
177-
return state;
178-
}
179-
});
168+
reducer({bar: 2});
169+
expect(spy.calls[3].arguments[0]).toMatch(
170+
/Unexpected key "bar".*reducer.*instead: "foo", "baz"/
171+
);
180172

181-
reducer({bar: 1});
182-
expect(spy.calls[0].arguments[0]).toMatch(
183-
/Unexpected key "bar".*instead: "foo"/
173+
reducer({bar: 2, qux: 4});
174+
expect(spy.calls[4].arguments[0]).toMatch(
175+
/Unexpected keys "bar", "qux".*reducer.*instead: "foo", "baz"/
184176
);
185177

186-
reducer({bar: 1});
187-
expect(spy.calls.length).toBe(1);
178+
reducer(1);
179+
expect(spy.calls[5].arguments[0]).toMatch(
180+
/reducer has unexpected type of "Number".*keys: "foo", "baz"/
181+
);
188182

189183
spy.restore();
190184
});

0 commit comments

Comments
 (0)