1 | import isFunction from 'lodash.isfunction';
|
2 | import last from 'lodash.last';
|
3 | import verify from '../util/verify';
|
4 |
|
5 | /**
|
6 | * Create a higher-order reducer by combining two or more reducers,
|
7 | * logically executing each in sequence (in essence combining their
|
8 | * functionality into one). This is useful when combining various
|
9 | * reducer types into one logical construct.
|
10 | *
|
11 | * **Please Note:** Because each reducer is able to build on what has
|
12 | * been accomplished by a prior reducer, joinReducers cumulatively
|
13 | * passes the state parameter that was returned from any prior reducer
|
14 | * (in the chain of reducers to execute). In essence this is an
|
15 | * accumulative process. While this does NOT relax the immutable
|
16 | * constraint of the reducer's state parameter, it is possible for a
|
17 | * down-stream reducer to receive a state parameter that is a
|
18 | * different instance from the start of the reduction process (because
|
19 | * an up-stream reducer needed to alter it in some way).
|
20 | *
|
21 | * The {{book.guide.devGuide}} discusses joinReducers() in more detail
|
22 | * (see {{book.guide.conceptJoin}}), and additional examples can
|
23 | * be found in {{book.guide.fullExample}}.
|
24 | *
|
25 | * @param {...reducerFn} reducerFns two or more reducer functions to join
|
26 | * together.
|
27 | *
|
28 | * @param {InitialState} [initialState] - the optional fall-back state
|
29 | * value used during the state initialization boot-strap process.
|
30 | *
|
31 | * @returns {reducerFn} a newly created reducer function (described above).
|
32 | */
|
33 | export default function joinReducers(...reducerFns) {
|
34 |
|
35 | // define our initialState parameter (optionally, the last parameter)
|
36 | // NOTE: We have to do this programatically because our function
|
37 | // accepts variable number of arguments.
|
38 | const initialState = isFunction(last(reducerFns)) ? undefined : reducerFns.pop();
|
39 |
|
40 | // validate params
|
41 | const check = verify.prefix('AstxReduxUtil.joinReducers() parameter violation: ');
|
42 |
|
43 | check(reducerFns && reducerFns.length >= 2,
|
44 | 'two or more reducerFn arguments are required');
|
45 |
|
46 | // ... each arg MUST be a function (reducerFn)
|
47 | const badArgNum = reducerFns.reduce( (firstBadArgNum, reducerFn, indx) => {
|
48 | return firstBadArgNum || (isFunction(reducerFn) ? 0 : indx+1);
|
49 | }, 0);
|
50 | check(!badArgNum,
|
51 | `argument position number ${badArgNum} is NOT a function ... expecting two or more reducerFns to join together`);
|
52 |
|
53 | // expose our new higher-order reducer
|
54 | return (state=initialState, action, originalReducerState) => {
|
55 |
|
56 | // maintain the originalReducerState as the immutable state
|
57 | // at the time of the start of the reduction process
|
58 | // ... in support of joinReducers()
|
59 | // ... for more info, refer to the Dev Guide {{book.guide.originalReducerState}}
|
60 | if (originalReducerState === undefined) {
|
61 | originalReducerState = state;
|
62 | }
|
63 |
|
64 | // execute each reducerFn in sequence
|
65 | return reducerFns.reduce( (nextState, reducerFn) => {
|
66 | return reducerFn(nextState, action, originalReducerState);
|
67 | },
|
68 | state);
|
69 |
|
70 | };
|
71 | }
|