Reducers in React - Making reducers lean and shedding that extra code.

Reducers in React - Making reducers lean and shedding that extra code.

Reducers are the basic building blocks of state handling in react.

But as my react project scaled with more and more states to handle, my reducer functions got bulkier with a lot of repeated code.

The below reducer is an example. If you look closely, you will be able to see many bits and pieces of code repeating every few lines.

If there was a cop in programming, I would have been arrested for violating the DRY principle.

Screenshot 2020-10-12 at 23.03.12.png

There are a few things that you can identify here directly, which usually remains the same across different reducers:

  1. A reducer is always a function that you pass to the root reducer.
  2. The reducer function usually takes a state object and an action object.
  3. The reducer block usually has a switch function that matches the appropriate action being resolved and returns the relevant data from the matching block.

After giving it a small thought ( 1 hour doesn't count so small ), I finally thought of creating a Reducer creator function that could abstract out the always and the usually appearing code that was re-occurring.

So, what I can think of:

  1. The creator function can generate me a function that takes in the arguments a reducer usually needs like the state and action payload.
  2. I can pass the creator function any defaultState that I need to set while creating a new reducer for my state.
  3. Since the switch case block is repeated across different reducers, I can ask the creator function to manage the action type lookup from a common piece of code rather than using it multiple times for multiple reducers. What if I passed just a list of actions that it needs to look up into for a specific reducer. That way I can restrict the scope of the reducer.

TL;DR

Ta-da! Let's see if the below function is able to resolve my requirements.

const ReducerCreator = ({ actionTypes = [], defaultState = {} }) => {
  return (state = defaultState, { type, payload = {} } = {}) => {
    if (actionTypes.includes(type)) {
      return { ...state, ...payload };
    }
    return state;
  }
}

So, while creating a reducer, I can just do:

const RootReducer = combineReducers({
  menu: ReducerCreator({ actionTypes: [ "a1", "a2", "a3" ], { isLoading: false }}),
  ...more such reducers
})

Whoa!, this makes me delete the whole menu reducer file I created in the first place. So, now when I add a new action type a4 in the actionTypes array, it already gets included in the scope of this reducer without writing any new cases under the switch block like the older implementation.

This removes the hard work of writing action types once in dispatching the action and once while resolving to the reducer.

This small piece of code is able to handle all reducer initializations that I needed to handle resulting in keeping my reducer as simple as possible.