Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 986 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Redux: Implementing combineReducers() from Scratch

4:22 JavaScript lesson by

Learn how to build a reasonable approximation of the combineReducers() utility in 15 lines. No magic!

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Learn how to build a reasonable approximation of the combineReducers() utility in 15 lines. No magic!

Avatar
Sequoia McDowell

I love that you explained what combineReducers does long-hand (writing it out explicitly) in the last video before introducing it here. That took the magic out of it and made it really easy to understand what's going on. Kudos!

Avatar
mobility-team

Great explanation, great content!

Avatar
Enrique

That was so meta, it made me go O_o

Avatar
Brad Tittle

Am I correct in saying the following:

Every event is dispatched to all of the reducers in combined reducers. If the action.type doesn't exist in the reducer it doesn't happen.

Or

If I wanted to have two different things happen with the same action, I can have to separate updates happen to the state. Let's say I am rolling the dice and tracking all rolls of the dice, I might keep a currentRoll running for display and a rollHistory for a fairness tracker. Since all actions pass through all reducers, I don't need to worry about managing which reducer is getting the action.

The more I look at this the more brilliant it is. The problem is attempting to pass it on to the next person.

I once got into an argument with someone about the ability of javascript to do multi dimensional arrays. He was fixated on the form array[ ][ ][ ][ ]. From what I can tell, he is correct that javascript doesn't do that. What is the difference between that and array[ { [ { [ ] } ] } ] and array [ ] [ ] [ ] [ ]. I think there are many differences. The question is, can I make the first one accomplish the tasks that the second one use to do. If n is small, it isn't that terrible. When n starts getting big, n^2 starts beating me up. But for most of the times I have run into multiple dimensions on arrays, the j, k and l usually stay small.

That is me attempting to say that communicating programming ideas to programmers can be challenging. Trying to communicate programming ideas to non programmers and have them actually hear what you said... That is how folks end up needing medication.

In the previous lesson, we learned to use the combineReducer function, which comes with Redux and generates one reducer from several other reducers, delegating to them paths of the state tree.

const { combineReducer } = Redux;
const todoApp = combineReducer({
  todos,
  visibilityFilter
});

To gain a deeper understanding of how exactly combinedReducers works, we will implement it from scratch in this lesson.

CombineReducers is a function, so I'm writing a function declaration. Its only argument is the mapping between the state keys and the reducers, so I'm just going to call it reducers.

const combineReducer = (reducers) => {

};

The return value is supposed to be a reducer itself, so this is a function that returns another function. The signature of the return function is a reducer signature. It has the state and the action.

const combineReducer = (reducers) => {
  return (state = {}, action) => {

  };
};

Now, I'm calling the Object.keys method, which gives me all the keys of the reducer's object. In our example, this is todos and the visibilityFilter.

Next, I'm calling the reduce method on the keys, because I want to produce a single value, such as the nextState, by accumulating over every reducer key and calling the corresponding reducer. Each reducer passed through the combineReducers function is only responsible for updating a part of the state. This is why I'm saying that the nextState by the given key can be calculated by calling the corresponding reducer by the given key with the current state by the given key and the action.

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

The array reduce wants me to return the next accumulated value from the call back, so I'm returning the nextState. I'm also specifying an empty object as the initial next state, before all the keys are processed.

There we have it. This is a working reimplementation of combinedReducers utility from Redux.

Let's briefly recap how it works. I'm calling combinedReducers with an object whose values are the reducer functions and keys are the state field they manage.

const todoApp = combineReducer({
  todos,
  visibilityFilter
});

Inside the generated reducer, I'm retrieving all the keys of the reducers I passed to combineReducers, which is an array of strings, todos and visibilityFilter.

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

I'm starting with an empty object for my next state and I'm using the reduce operation of these keys to fill it gradually.

Notice that I'm mutating the next state object on every iteration. This is not a problem, because it is the object I created inside the reducer. It is not something passed from outside, so reducer stays a pure function.

To calculate the next state for a given key, it calls the corresponding reducer function, such as todos or visibilityFilter.

(nextState, key) => {
  nextState[key] = reducers[key](
    state[key],
    action
  );
  return nextState;
}

The generated reducer will pass through the child reducer only if part of its state by the key. If its state is a single object, it's only going to pass the relevant part, such as todos or visibility filter, depending on the current key, and save the result in the next state by the same key.

Finally, we use the array reduce operation with the empty object as the initial next state, that is being filled on every iteration until it is the return value of the whole reduce operation.

In this lesson, you learned how to implement the combinedReducers utility that comes with Redux from scratch.

It is not essential to use in Redux, so it is fine if you don't fully understand how it works yet. However, it is a good idea to practice functional programming and understand functions can take other functions as arguments and return other functions, because knowing this will help you get more productive in Redux in the long term.



HEY, QUICK QUESTION!
Joel's Head
Why are we asking?