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
.
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
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.
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.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!
☞ JavaScript Programming Tutorial Full Course for Beginners
☞ Learn JavaScript - Become a Zero to Hero
☞ Javascript Project Tutorial: Budget App
☞ JavaScript for React Developers | Mosh
☞ E-Commerce JavaScript Tutorial - Shopping Cart from Scratch