Redux developers, please stop doing this!
Hồng Phát
Posted on August 17, 2024
I worked on a React project in 2019, I believe it was built on top of the react-boilerplate template, and the developer experience with Redux was so bad that I became a Vue developer.
After a few years working with Vuex, Pinia, Recoil (yes, I came back), and recently Jotai (which is my go-to state management library at the moment), a friend randomly asked for my help in debugging the flow of his app, and it amazed me how Redux developers still write that boilerplate code:
const cartSlice = createSlice({
name: "cart",
initialState: {
items: [],
total: 0,
// x, y, z,…
},
reducers: {
setTotal(state, action) {
const { total } = action.payload;
state.total = total;
},
pushItem(state, action) {
const { item } = action.payload;
state.items.push(item);
},
clearItems(state) {
state.items.length = 0;
},
// setX, setY, setZ…
},
});
This was exactly the reason why I had such a bad time maintaining that Redux project. And it was even worse without Redux Toolkit (which they only introduced recently):
export const SET_TOTAL = "SET_TOTAL";
export const setTotal = (total) => ({
type: SET_TOTAL,
payload: total,
});
switch (action.type) {
case SET_TOTAL: {
return {
...state,
total: action.payload,
};
}
default:
return state;
}
export const getTotal = createSelector(
(state) => state.total,
(total) => total
);
const mapStateToProps = (state) => ({
total: getTotal(state),
});
const mapDispatchToProps = (dispatch) => ({
setTotal: (total) => dispatch(setTotal(total)),
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
class MyComponent extends Component {
render() {
const { total } = this.props;
return <b>You must pay: {total}</b>;
}
}
This was not a joke! In fact, cumbersome, excessive boilerplate code like the example above was often considered "best practices" and "industry standards" in my observation. It was almost as if we were writing code like this:
function isDifferent(x, y) {
return x != y;
}
function isGreater(x, y) {
return x > y;
}
function subtract(x, y) {
return x - y;
}
function greatestCommonDivisor(x, y) {
while (isDifferent(y, 0)) {
if (isGreater(x, y)) {
x = subtract(x, y);
} else {
y = subtract(y, x);
}
}
return x;
}
The above code is far from being "highly scalable", providing "the best developer experience", or focusing on "performance and best practices".
Don’t get me wrong! Redux’s pattern itself is not bad; in fact, it’s so good that React introduced the useReducer hook to mimic it. The issue is that a marginal number of React developers are doing it wrong. Here’s how to do it right:
-
Batch relevant updates: Instead of creating data-oriented actions like
PUSH_ITEM
andSET_TOTAL
, focus on behavior-oriented actions likeADD_TO_CART
, which handle multiple updates (adding an item and updating the total) in one go. -
Keep the state simple and move complex logic to selectors: For example, you can remove computable fields like
total
from your state entirely, then calculate them automatically from cartitems
usingreselect
. This approach not only ensures data integrity but also makes the code easier to maintain.
Please share this post with anyone you know who writes boilerplate-heavy Redux code, so we can all make state management in single-page applications less of a pain!
Posted on August 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.