3 Ways improve Redux Reducers

3 Ways improve Redux Reducers

  • 500

Improving Redux Reducers in 3 Ways. Below is a simple `switch` statement that you probably have seen in 99% of the Redux/reducers examples out there.

In this article, I am going to assume you know what Redux is and what the reducers do.

I will go over how to improve your Redux reducers by making them faster and how to avoid the cyclomatic complexity warning/error you might get with something like SonarQube when the number of actions increases.

Below is a simple switch statement that you probably have seen in 99% of the Redux/reducers examples out there.

switch (action.type) {
  case ShowsAction.REQUEST_SHOW_FINISHED:
    return {
      ...state,
      show: action.payload,
    };
  case ShowsAction.REQUEST_EPISODES_FINISHED:
    return {
      ...state,
      episodes: action.payload,
    };
  default:
    return state;
}

reducer-switch-statement.js

The way we are going to improve it is by using a dictionary.

Dictionary (Key-Value Pair)

A dictionary is just a simple JavaScript object where you can add a string value as key and assign a value to it.

Below is a simplified version of what we are going to create. Notice how the key is the action type name and the assigned value is a function that takes the current state and payload as arguments to create a new state.

const dictionary = {};
// Add keys to the dictionary
dictionary['REQUEST_SHOW_FINISHED'] = (state, payload) => {
  return {
    ...state,
    show: payload,
  }
};
dictionary['REQUEST_EPISODES_FINISHED'] = (state, payload) => {
  return {
    ...state,
    episodes: payload,
  }
};
dictionary['REQUEST_CAST_FINISHED'] = (state, payload) => {
  return {
    ...state,
    actors: payload,
  }
};
// Usage
const newState = dictionary[action.type](state, action.payload); // Warning: This will break if the action.type is not found

dictionary.js

JavaScript Functional Approach

Let’s improve the dictionary example by creating a baseReducer function that takes the initialState as the first argument and a dictionary as the second argument.

Hopefully, the code below is easy to read/understand but, basically, we use the action type constant as the function name.

export const initialState = {
  currentShowId: '74',
  show: null,
  episodes: [],
  actors: [],
};

export const showsReducer = baseReducer(initialState, {
  [ShowsAction.REQUEST_SHOW_FINISHED](state, action) {
    return {
      ...state,
      show: action.payload,
    };
  },

  [ShowsAction.REQUEST_EPISODES_FINISHED](state, action) {
    return {
      ...state,
      episodes: action.payload,
    };
  },

  [ShowsAction.REQUEST_CAST_FINISHED](state, action) {
    return {
      ...state,
      actors: action.payload,
    };
  },
});

_showsReducer.js

export default function baseReducer(initialState, reducerDictionary) {
  // returns a redux reducing function
  return (state = initialState, action) => {
    // if the action type is used for a reducer name then this be a reference to it.
    const reducer = reducerDictionary[action.type];

    // if the action type "reducer" const is undefined or the action is an error
    // return the state.
    if (!reducer || action.error) {
      return state;
    }

    // if there is a valid reducer call it with the state and action objects.
    return reducer(state, action);
  };
}

baseReducer.js

In the above code, the reducerDictionary parameter is the dictionary that was passed in. Notice how action.type is used here, reducer[action.type], to get access to the correct reducer function.

JavaScript Class Approach

Let’s improve the dictionary example by creating a BaseReducer class for our class reducers to extend.

Below, notice how ShowsReducer extends BaseReducer. This is inheritance and it abstracts some of the logic to another class so the reducers only have the necessary stuff.

export default class ShowsReducer extends BaseReducer {
  initialState = {
    currentShowId: '74',
    show: null,
    episodes: [],
    actors: [],
  };

  [ShowsAction.REQUEST_SHOW_FINISHED](state, action) {
    return {
      ...state,
      show: action.payload,
    }
  }

  [ShowsAction.REQUEST_EPISODES_FINISHED](state, action) {
    return {
      ...state,
      episodes: action.payload,
    }
  }

  [ShowsAction.REQUEST_CAST_FINISHED](state, action) {
    return {
      ...state,
      actors: action.payload,
    }
  }
}

_ShowsReducer.js

export default class BaseReducer {
  initialState = {};

  reducer = (state = this.initialState, action) => {
    const method = this[action.type];

    if (!method || action.error) {
      return state;
    }

    return method.call(this, state, action);
  };
}

BaseReducer.js

If you look at the above BaseReducer, you will see:

  • Line 2: Is the initialState that will be overridden when a reducer class extends this BaseReducer.
  • Line 4: Is the reducer method that will be used by Redux.
  • Line 5: Gets access to the class method that matches the action.type.
  • Line 7: If the method is not found (!method) or if the action is an error (action.error), then it returns the current state.
  • Line 11: Calls the found method with the state and action arguments which will return the modified state that Redux will use.

Code Examples

If you want to see these code examples in action, check out my other article for the sample application and source code for both the functional and class-base approaches. I have TypeScript versions too!