blog.jakoblind.no

Code your own combineReducers

Getting tired of following yet another tutorial on how to use Redux?

It’s time for a completely different approach. To master Redux, we will code it from scratch. Today it’s time for the combineReducer function.

What is a reducer

Before we start, we need a quick reminder what a reducer is.

A reducer is a function that takes the Redux state and a Redux Action as input parameters. It uses the input parameters to generate a new state that the function returns.

An example of a simple reducer looks like this:

function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1
    case "DECREMENT":
      return state - 1
    default:
      return state
  }
}

As you can see the reducer function is basically a long switch.

When the application grows with more and more actions, the reducer function grows with it. We want to avoid large functions because they are difficult to overview, extend and combine.

There is a function called combineReducer that is shipped with Redux. It lets us split the reducer up to smaller reducers and combine them so we can use them in Redux.

Let’s code our own combineReducer!

We will start by copying the example usage from the official docs:

// reducers.js
export default theDefaultReducer = (state = 0, action) => state

export const firstNamedReducer = (state = 1, action) => state

export const secondNamedReducer = (state = 2, action) => state

// Use ES6 object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
  theDefaultReducer,
  firstNamedReducer,
  secondNamedReducer,
})

const store = createStore(rootReducer)
console.log(store.getState())
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}

We are going to use this as a starting point for our own implementation.

Function definition

The combineReducer function call takes an object with reducers. Let’s start by implementing support for that.

function combineReducers(reducers) {}

We now have the function combineReducers that accepts reducers as input parameters. Sweet!

Return a new empty reducer

Look at the example again. combineReducer returns a rootReducer that is used as an argument to createStore. In other words, the rootReducer works just like a normal Redux reducer.

As we learned in the beginning in this article, a normal Redux reducer is a function that takes two parameters: state and action. Let’s return that.

function combineReducers(reducers) {
  return function combination(state = {}, action) {}
}

Return a reducer that creates a new state

In the example, there is a console.log of the state of the store. We can see the expected output in the comments. The output after running getState should be an object with all the reducer names as keys and the state as the value.

Let’s start by returning an object with the reducer names as keys. To simplify we will have a hardcoded string as values.

function combineReducers(reducers) {
  // First get an array with all the keys of the reducers (the reducer names)
  const reducerKeys = Object.keys(reducers)

  return function combination(state = {}, action) {
    // This is the object we are going to return.
    const nextState = {}

    // Loop through all the reducer keys
    for (let i = 0; i < reducerKeys.length; i++) {
      // Get the current key name
      const key = reducerKeys[i]
      nextState[key] = "Here is where the state will be"
    }
    return nextState
  }
}

When we run this code we will get this output:

// {theDefaultReducer : "Here is where the state will be", firstNamedReducer : "Here is where the state will be", secondNamedReducer : "Here is where the state will be"}

We are almost there! The final thing we need to do is to print the object with the correct values.

Return a reducer that returns correct state

Let’s finish this up by adding the correct state of each sub reducer as value to the printed object. To do that we will call each sub reducer.

function combineReducers(reducers) {
  // First get an array with all the keys of the reducers (the reducer names)
  const reducerKeys = Object.keys(reducers)

  return function combination(state = {}, action) {
    // This is the object we are going to return.
    const nextState = {}

    // Loop through all the reducer keys
    for (let i = 0; i < reducerKeys.length; i++) {
      // Get the current key name
      const key = reducerKeys[i]
      // Get the current reducer
      const reducer = reducers[key]
      // Get the the previous state
      const previousStateForKey = state[key]
      // Get the next state by running the reducer
      const nextStateForKey = reducer(previousStateForKey, action)
      // Update the new state for the current reducer
      nextState[key] = nextStateForKey
    }
    return nextState
  }
}

Now we are done! We have written the whole combineReducers function. Not that complicated, was it?

Take a look at the “real” combineReducer function and compare (it’s the function at the bottom). The real one has more error handling and some optimizations.

But the core of it is the same as the one we just created!

Next step

Learn about the source code of Redux Thunk in the next post: Async Actions with Redux Thunk demystified


tags: