snippetjavascriptTip
Writing readable reducers in Redux
Viewed 0 times
readablereactreducersreduxwriting
Problem
_This article's examples are based on Redux, where the issues described are more common. As these issues are not limited to Redux, you might still find some value in the tips and solutions presented if you are struggling with maintaining complexity and readability in your code._
When working with state in your code, you might often run into issues with maintaining complexity, keeping the code readable and even figuring out how to properly test it. Oftentimes, these issues are easily fixable if you take a step back and identify the root of the problem.
Let's start with an example of what a redux reducer might look like. We'll follow this example throughout this post, making changes and improvements, so make sure you understand it before continuing.
While the code in the example is not that complicated right now, complexity can increase very fast as more action types need to be handled by our application. This is due to the fact that each
Another issue we can identify is that each
When working with state in your code, you might often run into issues with maintaining complexity, keeping the code readable and even figuring out how to properly test it. Oftentimes, these issues are easily fixable if you take a step back and identify the root of the problem.
Let's start with an example of what a redux reducer might look like. We'll follow this example throughout this post, making changes and improvements, so make sure you understand it before continuing.
While the code in the example is not that complicated right now, complexity can increase very fast as more action types need to be handled by our application. This is due to the fact that each
action.type's logic is nested inside the reducer function, thus adding more code and complexity with each new action.Another issue we can identify is that each
action has a different structure, which increases cognitive load for future maintainers, as they have to remember what keys their action needs to have. There's also the added issue of running into a case where action.type might be needed to pass actual data to the state (i.e. state.type could exist).Solution
const initialState = {
id: null,
name: '',
properties: {},
};
const generateID = () => Math.floor(Math.random() * 1000);
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'createID':
return {
...state,
id: generateID(),
};
case 'setName':
return {
...state,
name: action.name,
};
case 'addProperty':
return {
...state,
properties: {
...state.properties,
[action.propertyName]: action.propertyValue,
},
};
case 'removeProperty':
return {
...state,
properties: Object.keys(state.properties).reduce((acc, key) => {
if (key !== action.propertyName) acc[key] = state.properties[key];
return acc;
}, {}),
};
default:
return state;
}
};Let's start with an example of what a redux reducer might look like. We'll follow this example throughout this post, making changes and improvements, so make sure you understand it before continuing.
While the code in the example is not that complicated right now, complexity can increase very fast as more action types need to be handled by our application. This is due to the fact that each
action.type's logic is nested inside the reducer function, thus adding more code and complexity with each new action.Another issue we can identify is that each
action has a different structure, which increases cognitive load for future maintainers, as they have to remember what keys their action needs to have. There's also the added issue of running into a case where action.type might be needed to pass actual data to the state (i.e. state.type could exist).Finally, our
action.type values are hardcoded inside the reducer function, making it hard to remember and sync across other files and components. This might seem like the least of our problems, but it's probably the easiest one to fix, so let's start there.Starting with removing the hardcoded strings for each of the
action.type values, we can make the code more maintainable and easier to read by extracting them to an object:Our
action objects aren't consistent in terms of structure with the exception of sharing a type key which we use to identify each action. If we hope to reduce mental strain and minimize headaches, we should make these more consistent. The easiest way to do so would be to put the whole action payload under a top-level key and nest any values passed to the action inside it:Code Snippets
const initialState = {
id: null,
name: '',
properties: {},
};
const generateID = () => Math.floor(Math.random() * 1000);
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'createID':
return {
...state,
id: generateID(),
};
case 'setName':
return {
...state,
name: action.name,
};
case 'addProperty':
return {
...state,
properties: {
...state.properties,
[action.propertyName]: action.propertyValue,
},
};
case 'removeProperty':
return {
...state,
properties: Object.keys(state.properties).reduce((acc, key) => {
if (key !== action.propertyName) acc[key] = state.properties[key];
return acc;
}, {}),
};
default:
return state;
}
};const ACTION_TYPES = {
CREATE_ID: 'createID',
SET_NAME: 'setName',
ADD_PROPERTY: 'addProperty',
REMOVE_PROPERTY: 'removeProperty'
};// Structure of any action passed to our reducer function
const action = {
// Any of the previously defined action types
type: ACTION_TYPES.CREATE_ID,
// Nest name, propertyValue and propertyKey inside this object
payload: { /* ... */ }
}Context
From 30-seconds-of-code: redux-readable-reducers
Revisions (0)
No revisions yet.