Ronaldo Vitto Lewerissa

Software engineering learning documentation.

Dissecting Redux (Part 2)

combineReducers implementation

As the name goes, this built-in function by Redux is use to get your reducers combine together.

const combineReducers = (reducer) => {  
  return (state = {}, action) => {
    return Object.keys(reducers).reduce((nextState, key) => {
      nextState[key] = reducers[key](state[key], action);
      return nextState;
    }, {});
  };
}

If this seems overwhelming, you might want to check this one, it does the same thing (with manual configuration).

const combineReducers = () => {  
  return (state = {}, action) => {
    return {
      substate1: subReducer1(state.substate1, action),
      substate2: subReducer2(state.substate2, action),
    };
  }
}

combineReducers produces a reducer that takes a state and an action

But instead of manipulating the state based on action it received, it passes the same action and a small portion of the state to another reducer (subReducer1, subReducer2), and let them handle it for you.

The resultant would be part of the next state.

combineReducers basically includes a reducer with another reducer. That’s why reducers are awesome. It’s composable.

If you when through the first implementation. Dan Abramov, the creator of Redux, use .reduce() method built-in into arrays. That’s why he named it reducer in the first place.

You might notice that he uses Object.keys() over for … in to traverse among reducers (object).

That is because it’s more efficient. You see, for … in traverse all methods/properties including their prototypes, so it’s kinda sucks.

Object.keys() generates an array of containing list of your object’s key.

Now, back into .reduce().

That method accepts two arguments, the second one is optional. The first one is a callback.

What it does is it evaluates over two values at a time (starting from index zero, so it initially it evaluates index zero and index one), the result would be evaluated to the next index, until it results into a single value.

It will not start from index zero if you specify an initial value (second argument), instead it will evaluate that initial value with your index zero.

In this case, Dan specifies an empty plain object as an initial value. He will keep passing this object as the result of the evaluation.

Now, what he does using that plain object/object literal — {} is fairly the same as you would see in the second implementation.

It calls the reducer:

reducers[key](state[key], action)  

and assign it to the corresponding key nextState[key] . Notice that *nextState *is the plain object we specify earlier.

Extra

If you’re still new to reducer, it’s basically a function that accepts previous state and an action, and returns a new state.

It has to be pure, which means you can’t mutate your arguments, because it might affect other reducers in an interesting way.

function reducer(prevState = {}, action) {  
  switch (action) {
    case 'ACTION_1':
      doSomething();
      break;
    case 'ACTION_2':
      doSomething();
      break;
    default:
      return prevState;
  }
}

This block prevState = {} means that if it receives undefined value, then it will assigns prevState with {}. We use it to supply the reducer with an initial state, we do this because initially our store will dispatch an empty action — thus undefined.

The default statement is necessary in case your reducer has no idea how to deal with the action, so you’ll just return the same state as it was.

Written by Ronaldo Vitto Lewerissa

Read more posts by this author.