Practical Redux Course

Avi Kaminetzky
4 min readJan 2, 2018

--

Currently I’m working through Mark Erikson’s course, “Practical Redux” (GitHub repo). The course is forcing me to think about functional JS patterns, Redux and its ecosystem. I will use this post to document any interesting things I learn along the way, specifically parts with a hard learning curve.

So far this post explains createReducer() and reduceReducers().

1. createReducer()

“We’re using one of the umpteen million createReducer utility functions out there, which lets you define separate reducer functions and a a lookup table instead of switch statements.” (Mark)

export function createReducer(initialState, fnMap) {

return (state = initialState, {type, payload}) => {

const handler = fnMap[type];

return handler ? handler(state, payload) : state;
};
}

createReducer() is a closure, so the inner function (the actual reducer) has access to initialState of its respective slice of the state. fnMap is an object with action types as the key and functions that calculate the new state as the value.

Let’s look at the tabsReducer.js which calls createReducer() to see an example function call:

const initialState = {
currentTab : "unitInfo",
};

export function selectTab(state, payload) {
return {
currentTab : payload.tabName,
};
}

export default createReducer(initialState, {
[TAB_SELECTED] : selectTab,
});

initialState is the default state for the tab slice of our state. selectTab makes use of the reselect library to calculate the new state. (For the benefits and how to use selectors in Redux, see ReactCasts and Redux docs). createReducer() takes the initialState and an object which lists all tab related actions and corresponding reducer functions.

For clarity’s sake, I will copy the TAB_SELECTED action as well:

export function selectTab(tabName) {
return {
type : TAB_SELECTED,
payload : {tabName},
};
}

Recap

We can now describe the steps of the inner function in createReducer():

  • Confirms that fnMap contains action type [‘TAB_SELECTED’]
  • Uses handler to store the value of action type in fnMap [selectTab]
  • handler reduces (calculates) the new state for its respective action type.

2. reduceReducer()

reduceReducers is a nifty little utility. It lets us supply multiple reducer functions as arguments and effectively forms a pipeline out of those functions, then returns a new reducer function. If we call that new reducer with the top-level state, it will call the first input reducer with the state, pass the output of that to the second input reducer, and so on. (If I were more Functional-Programming-minded, I’d guess that there’s probably a lot of similarities with compose(), but this is about as far as my mind goes in that direction. I’m sure someone will be happy to correct me or clarify things.) (Mark)

export function reduceReducers(...reducers) {
return (previous, current) =>
reducers.reduce(
(p, r) => r(p, current),
previous
);
}

Utility

To illustrate the utility of the function, here is a quote from StackOverflow using the addAndMult() function as an example of a reduceReducers() function:

const addAndMult = reducerReduce(reducerAdd, reducerMult) const initial = addAndMult(undefined)
/*
* {
* sum: 0,
* totalOperations: 0
* }
*
* First, reducerAdd is called, which gives us initial state { sum: 0 }
* Second, reducerMult is called, which doesn't have payload, so it
* just returns state unchanged.
* That's why there isn't any `product` prop.
*/
const next = addAndMult(initial, 4)
/*
* {
* sum: 4,
* product: 4,
* totalOperations: 2
* }
*
* First, reducerAdd is called, which changes `sum` = 0 + 4 = 4
* Second, reducerMult is called, which changes `product` = 1 * 4 = 4
* Both reducers modify `totalOperations`
*/
const final = addAndMult(next, 4)
/*
* {
* sum: 8,
* product: 16,
* totalOperations: 4
* }
*/

In other words, addAndMult(), can apply a single action object to every part of the state object, using different reducers functions in succession. So if we had this action:

{ type: ‘ADD_AND_MULTIPLY', PAYLOAD: 4}

We can modify the value of the sum key in state with two separate reducers in succession, first the addReducer() and then the multiplyReducer().

How reduceReducers() Actually Works

export function reduceReducers(...reducers) {
return (previous, current) =>
reducers.reduce(
(p, r) => r(p, current),
previous
);
}

I asked for help on StackOverflow and the community was very helpful. I will base the following off one particular answer, since I like its granularity and context.

Lets do a basic recap of the reduce() function:

The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (MDN)

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

For added clarity, we will convert our function from the arrow syntax to the more verbose function syntax and rename the function arguments.

export function reduceReducers(...reducersArray) {
return function (previousState, currentAction) {
const reducerFunction = function (accumulator, currentReducer) {
return currentReducer(accumulator, currentAction);
}

return reducersArray.reduce(reducerFunction, previousState);
}
}

reduceReducer() is a closure, a function which creates another function. So what does each function argument mean?

  • reducersArray: An array of reducer functions (cloned with the spread operator).
  • previousState: state object before reduceReducers() does its magic (state version 1).
  • currentAction: action object which is called on each reducer function.
  • accumulator: It makes sense to think about a sequence of events. Stage 1 — previousState (state version 1) is operated on by the first redux reducer function in reducersArray, the result is the accumulator (state version 2). Stage 2accumulator is operated on by the second redux reducer function in reducersArray, the result now becomes accumulator (state version 3). And so on…

End result? A function which takes a state object and a pipeline of reducers, , calls reducer #2 on the calculated state of reducer #1, then reducer #3 on the calculated state of reducer #2, etc.

It’s important to note that we are dealing with an ordered array, so the functions in reducerArray will be called in succession.

Recap

Whereas the combineReducers() function would like each and every slice of state operated on by unique actions and reducers, reduceReducers() would like the entire state object to be operated on by every single reducer, one after another, when a particular action is called.

--

--