DEV Community

Sam Markham
Sam Markham

Posted on • Originally published at sharkham.github.io

Dealing with overlapping reducers in Redux

(originally published April 25, 2020)

My final project for Flatiron School got a lot more complicated than I was expecting it to with reducers, and there were many little finnicky things here that tripped me up, even into the last hours of coding! This made me think it might be a good thing to write a blog about.

To get briefly into the context, my project is a novel tracker app where users can keep track of their writing progress when participating in a novel writing contest. The website's functionality depends on having access to:

  • a current user
  • their current novel (from this year, as the contest is meant to run every year)
  • all the novels in this year's contest
  • (it also includes a few other things, but we don't need to worry about them here!)

With Redux, I could keep all of these things in store, which can be accessed from any component in the app--super handy!--and with Redux's combineReducers function, I could make a store with different reducers for each key I wanted in store.

import { combineReducers } from 'redux'
import allCurrentNovels from './allCurrentNovels'
import currentUser from './currentUser'
import currentNovel from './currentNovel'

const rootReducer = combineReducers({
  currentUser,
  allCurrentNovels,
  currentNovel
})

export default rootReducer;
Enter fullscreen mode Exit fullscreen mode

The fun part came when I decided I wanted the currentNovel to also be part of allCurrentNovels, so that I could easily access the main novel in question for most of the app but also have that novel be visible to the user on the main page along with all the other novels in the contest.

Basically: when using rootReducer, each reducer going into it is responsible for its own piece of the store. currentNovel is either set to null or a user's novel. allCurrentNovels includes everything. What this means practically is that every change to currentNovel in its reducer must also find and change that novel in the allCurrentNovels array, because the novel is also in there, and how it appears there influences how it looks in the app somewhere else.

Here are a couple of excerpts from switch statements in each reducer:

// /reducers/currentNovel.js
...
    case 'UPDATE_NOVEL':
      return action.novel
    case 'ADD_BADGE':
      return {
        ...state,
        badges: [...state.badges, action.badge]
      }
            ...

// /reducers/allCurrentNovels.js
... 
    case 'ADD_NOVEL':
      return [...state, action.novel]
    case 'ADD_BADGE':
      return state.map(novel => {
        if (novel.id === action.badge.novel_id) {
          return {
            ...novel,
            badges: [...novel.badges, action.badge]
          };
        } else {
          return novel;
        }
      })
...
Enter fullscreen mode Exit fullscreen mode

The currentNovel reducer is only responsible for the key of currentNovel in the Redux store, which is either null or an object, so it only has to return the payload of the action dispatched to it. The allCurrentNovels reducer is responsible for an array of novels, so adding a currentNovel needs to affect it too--but because it needs to hold all of the other novels too, the new state it returns spreads the old state into a new array which also includes the new novel object.

Similarly, with adding a badge to the currentNovel, the currentNovel reducer only has to return an object that spreads the rest of the pre-existing state into it, and then spreads the pre-existing badges state into the novel's badges array while also adding the new badge. The allCurrentNovels reducer needs to deal with this action in a similar way, but it needs to find the right novel before modifying it.

While my project set up might be a bit of a fringe case for this, how an action might need to go through multiple reducers is a good thing to keep in mind for a variety of different scenarios. Another one from my project that comes to mind is that when an action with the type "CLEAR_CURRENT_USER" fires, it needs to be in the currentUser reducer to set the currentUser object to null, but also in the currentNovel reducer to set that object to null--and in whatever other reducers deal with pieces of the store that should be influenced by a user logging out.

I hope this is helpful to anyone else struggling to get their head around multiple reducers like I was!

Oldest comments (0)