Reducer Separation

Learn how to separate reducers to make the code simple and more manageable.

The obvious solution would be to find a way to split the reducer code into multiple files or multiple reducers. Since createStore() receives only one reducer, it will be that reducer’s job to call other reducers to help it calculate the new state.

The simplest method of determining how to split the reducer code is by examining the state we need to handle. Therefore we first declare the state as a const:

const state = {
  recipes: [],
  ingredients: [],
  ui: {}
}

We can now create three different reducers, each responsible for part of the state:

const recipesReducer = (state, action) => {};
const ingredientsReducer = (state, action) => {};
const uiReducer = (state, action) => {};

const reducer = (state, action) => {
  // TODO: combine the result of all substate reducers
}

Since each of the reducers calculates a new state, we can build a new state by calling all the reducers one after another:

//declaration of reducers
const recipesReducer = (state, action) => {};
const ingredientsReducer = (state, action) => {};
const uiReducer = (state, action) => {};

const reducer = (state, action) => {
// let newState equal to the previous state.
  let newState = state;

//pass newState through all the reducers so that the state is updated
  newState = recipesReducer(newState, action);
  newState = ingredientsReducer(newState, action);
  newState = uiReducer(newState, action);

  return newState;
}

While this approach works correctly, you might have noticed a potential problem. Why does the recipesReducer reducer need to access and calculate the whole state instead of only the recipes substate? We can further improve our reducers by having each act solely it’s corresponding substate:

//declaration of reducers
const recipesReducer = (recipes, action) => {};
const ingredientsReducer = (ingredients, action) => {};
const uiReducer = (ui, action) => {};

//create a newState that is a combination of the state given by the reducers. 
const reducer = (state, action) => {
  const newState = Object.assign({}, state, {

  //pass only the relevant objects within the state to each reducer
    recipes: recipesReducer(state.recipes, action),
    ingredients: ingredientsReducer(state.ingredients, action),
    ui: uiReducer(state.ui, action)
  });

  return newState;
}

With this new code, each reducer receives only the part of the state relevant to it and can’t affect other parts.

  • This separation proves very powerful in large-scale projects. It means developers can rely on reducers to modify only the parts of the state they are connected to and never cause clashes.
  • Another side effect of this separation of concerns is that our reducers become much more straightforward.

Since they no longer have to calculate the whole state, a large part of the code is no longer needed:


const recipesReducer = (state, action) => {
  // switch statement to check the action type
  switch (action.type) {
    case ADD_RECIPE:
      // returns a new state that combines previous recipes with the new recipes in action.payload
      return Object.assign({}, state, {
        recipes: state.recipes.concat(action.payload);
      })
    ...
  }
}

We can write this simply as:


const recipesReducer = (recipes, action) => {
  switch (action.type) {
    case ADD_RECIPE:
      return recipes.concat(action.payload);
    ...
  }
}

Get hands-on with 1200+ tech skills courses.