Skip to content

Commit a1485f0

Browse files
committed
Refactor warnings and errors in combineReducers()
1 parent 6fd9b05 commit a1485f0

File tree

1 file changed

+45
-40
lines changed

1 file changed

+45
-40
lines changed

src/utils/combineReducers.js

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import pick from '../utils/pick';
55

66
/* eslint-disable no-console */
77

8-
function getErrorMessage(key, action) {
8+
function getUndefinedStateErrorMessage(key, action) {
99
var actionType = action && action.type;
1010
var actionName = actionType && `"${actionType.toString()}"` || 'an action';
1111

@@ -15,36 +15,34 @@ function getErrorMessage(key, action) {
1515
);
1616
}
1717

18-
function verifyStateShape(inputState, outputState, action) {
18+
function getUnexpectedStateKeyWarningMessage(inputState, outputState, action) {
1919
var reducerKeys = Object.keys(outputState);
2020
var argumentName = action && action.type === ActionTypes.INIT ?
2121
'initialState argument passed to createStore' :
2222
'previous state received by the reducer';
2323

2424
if (reducerKeys.length === 0) {
25-
console.error(
25+
return (
2626
'Store does not have a valid reducer. Make sure the argument passed ' +
2727
'to combineReducers is an object whose values are reducers.'
2828
);
29-
return;
3029
}
3130

3231
if (!isPlainObject(inputState)) {
33-
console.error(
32+
return (
3433
`The ${argumentName} has unexpected type of "` +
3534
({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
3635
`". Expected argument to be an object with the following ` +
3736
`keys: "${reducerKeys.join('", "')}"`
3837
);
39-
return;
4038
}
4139

4240
var unexpectedKeys = Object.keys(inputState).filter(
4341
key => reducerKeys.indexOf(key) < 0
4442
);
4543

4644
if (unexpectedKeys.length > 0) {
47-
console.error(
45+
return (
4846
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
4947
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
5048
`Expected to find one of the known reducer keys instead: ` +
@@ -53,6 +51,34 @@ function verifyStateShape(inputState, outputState, action) {
5351
}
5452
}
5553

54+
function assertReducerSanity(reducers) {
55+
Object.keys(reducers).forEach(key => {
56+
var reducer = reducers[key];
57+
var initialState = reducer(undefined, { type: ActionTypes.INIT });
58+
59+
if (typeof initialState === 'undefined') {
60+
throw new Error(
61+
`Reducer "${key}" returned undefined during initialization. ` +
62+
`If the state passed to the reducer is undefined, you must ` +
63+
`explicitly return the initial state. The initial state may ` +
64+
`not be undefined.`
65+
);
66+
}
67+
68+
var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
69+
if (typeof reducer(undefined, { type }) === 'undefined') {
70+
throw new Error(
71+
`Reducer "${key}" returned undefined when probed with a random type. ` +
72+
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
73+
`namespace. They are considered private. Instead, you must return the ` +
74+
`current state for any unknown actions, unless it is undefined, ` +
75+
`in which case you must return the initial state, regardless of the ` +
76+
`action type. The initial state may not be undefined.`
77+
);
78+
}
79+
});
80+
}
81+
5682
/**
5783
* Turns an object whose values are different reducer functions, into a single
5884
* reducer function. It will call every child reducer, and gather their results
@@ -74,54 +100,33 @@ export default function combineReducers(reducers) {
74100
var finalReducers = pick(reducers, (val) => typeof val === 'function');
75101
var sanityError;
76102

77-
Object.keys(finalReducers).forEach(key => {
78-
var reducer = finalReducers[key];
79-
var initialState;
80-
81-
try {
82-
initialState = reducer(undefined, { type: ActionTypes.INIT });
83-
} catch (e) {
84-
sanityError = e;
85-
}
86-
87-
if (!sanityError && typeof initialState === 'undefined') {
88-
sanityError = new Error(
89-
`Reducer "${key}" returned undefined during initialization. ` +
90-
`If the state passed to the reducer is undefined, you must ` +
91-
`explicitly return the initial state. The initial state may ` +
92-
`not be undefined.`
93-
);
94-
}
95-
96-
var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
97-
if (!sanityError && typeof reducer(undefined, { type }) === 'undefined') {
98-
sanityError = new Error(
99-
`Reducer "${key}" returned undefined when probed with a random type. ` +
100-
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
101-
`namespace. They are considered private. Instead, you must return the ` +
102-
`current state for any unknown actions, unless it is undefined, ` +
103-
`in which case you must return the initial state, regardless of the ` +
104-
`action type. The initial state may not be undefined.`
105-
);
106-
}
107-
});
103+
try {
104+
assertReducerSanity(finalReducers);
105+
} catch (e) {
106+
sanityError = e;
107+
}
108108

109109
var defaultState = mapValues(finalReducers, () => undefined);
110110

111111
return function combination(state = defaultState, action) {
112112
if (sanityError) {
113113
throw sanityError;
114114
}
115+
115116
var finalState = mapValues(finalReducers, (reducer, key) => {
116117
var newState = reducer(state[key], action);
117118
if (typeof newState === 'undefined') {
118-
throw new Error(getErrorMessage(key, action));
119+
var errorMessage = getUndefinedStateErrorMessage(key, action);
120+
throw new Error(errorMessage);
119121
}
120122
return newState;
121123
});
122124

123125
if (process.env.NODE_ENV !== 'production') {
124-
verifyStateShape(state, finalState, action);
126+
var warningMessage = getUnexpectedStateKeyWarningMessage(state, finalState, action);
127+
if (warningMessage) {
128+
console.error(warningMessage);
129+
}
125130
}
126131

127132
return finalState;

0 commit comments

Comments
 (0)