-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathcombineReducers.js
134 lines (114 loc) · 4.59 KB
/
combineReducers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import Immutable from 'immutable';
// TODO need to find a way to reference Redux's init for compatability
const ActionTypes = { INIT: 'INIT' };
const isImmutable = (obj) => {
return Immutable.Iterable.isIterable(obj);
};
/* eslint-disable no-console */
function getErrorMessage(key, action) {
var actionType = action && action.type;
var actionName = actionType && `"${actionType.toString()}"` || 'an action';
return (
`Reducer "${key}" returned undefined handling ${actionName}. ` +
`To ignore an action, you must explicitly return the previous state.`
);
}
function verifyStateShape(initialState, currentState) {
var reducerKeys = currentState.keySeq();
if (reducerKeys.size === 0) {
console.error(
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
);
return;
}
if (!isImmutable(initialState)) {
console.error(
'initialState has unexpected type of "' +
({}).toString.call(initialState).match(/\s([a-z|A-Z]+)/)[1] +
'". Expected initialState to be an instance of Immutable.Iterable with the following ' +
`keys: "${reducerKeys.join('", "')}"`
);
return;
}
const unexpectedKeys = initialState.keySeq().filter(
key => reducerKeys.indexOf(key) < 0
);
if (unexpectedKeys.size > 0) {
console.error(
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" in initialState will be ignored. ` +
`Expected to find one of the known reducer keys instead: "${reducerKeys.join('", "')}"`
);
}
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @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) {
reducers = isImmutable(reducers) ? reducers : Immutable.fromJS(reducers);
const finalReducers = reducers.filter(v => typeof v === 'function');
finalReducers.forEach((reducer, key) => {
if (typeof reducer(undefined, { type: ActionTypes.INIT }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined.`
);
}
var type = Math.random().toString(36).substring(7).split('').join('.');
if (typeof reducer(undefined, { type }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined.`
);
}
});
var defaultState = finalReducers.map(r => undefined);
var stateShapeVerified;
return function combination(state = defaultState, action) {
let finalState = state;
finalReducers.forEach( ( reducer, key ) => {
const oldState = state.get( key );
const newState = reducer( oldState, action );
if (typeof newState === 'undefined') {
throw new Error(getErrorMessage(key, action));
}
finalState = finalState.set( key, newState );
});
if ((
// Node-like CommonJS environments (Browserify, Webpack)
typeof process !== 'undefined' &&
typeof process.env !== 'undefined' &&
process.env.NODE_ENV !== 'production'
) ||
// React Native
typeof __DEV__ !== 'undefined' &&
__DEV__ // eslint-disable-line no-undef
) {
if (!stateShapeVerified) {
verifyStateShape(state, finalState);
stateShapeVerified = true;
}
}
return finalState;
};
}