STOP using Redux blindly!
STOP using Redux blindly!
As a lazy developer — I didn’t buy it. I felt like using the default state and context was way better. There was way too much boilerplate in Redux and the separation of concerns although valid, could just as easily be accomplished using a custom hook. We also used redux-saga for interacting with the backend. What felt like another highly unnecessary burden to me.
But I was beginning my career — so I thought I’d give it a fair try. I used redux and redux-saga for about a year. And my arguments against it only intensified. I kept asking — why are we using redux, and my seniors gave me answers like —
- It makes the code more testable
- It separates the business logic from the component logic
- It is recommended to not use useState for complex data types (like objects or arrays of objects)
- redux-saga can give you more control over the API state
- You could use context, but how many context providers will you wrap your app in?
To all these, I had answers, but not super-convincing ones. I mean, redux was redux — used by millions of developers and large companies — and I was just a junior software engineer with frustration over boilerplate.
Now, after having worked as a Team Lead, I can confidently say, my seniors were all wrong — and they would agree. My reason for writing this article is that even today, there are so many companies that use redux and accept it as a blind standard. We need to stop and look at the alternatives we have!
To start with, let’s understand the problem I have with redux more clearly. Here is the amount of code we would need to write for a simple count state using redux. Obviously, this is just an example — doesn’t showcase a real application, but it’s quite effective.
// src/actionConstants.js
const ACTION_CONSTANTS = {
INCREMENT_COUNT: "INCREMENT_COUNT",
DECREMENT_COUNT: "DECREMENT_COUNT",
RESET_COUNT: "RESET_COUNT"
}
// src/store/countStore/reducer.js
const initialState = { count: 0 }
export const countReducer = (state = initialState, action) => {
switch (action.type){
case ACTION_CONSTANTS.INCREMENT_COUNT:
return { count: state.count + 1 };
case ACTION_CONSTANTS.DECREMENT_COUNT:
return { count: state.count - 1 };
case ACTION_CONSTANTS.RESET_COUNT:
return initialState;
}
}
// src/store/countStore/selectors.js
export const useCountReducer = () => {
return useSelector(store => store.countReducer)
}
// src/store/countStore/actions.js
export const incrementCount = () => ({
type: ACTION_CONSTANTS.INCREMENT_COUNT
})
export const decrementCount = () => ({
type: ACTION_CONSTANTS.DECREMENT_COUNT
})
export const resetCount = () => ({
type: ACTION_CONSTANTS.RESET_COUNT
})
// src/store/index.js
const rootReducer = combineReducers({ countReducer: countReducer, ... });
Isn’t it just criminal? This still doesn’t account for the code going into your saga or thunk when interacting with the backend — this is just for a regularly shared state variable — count. Imagine the amount of trouble you have to go through just to make a minor change to the logic — jumping from one file to another and making sure those changes are consistent across files.
Redux is meant to be a global store. It is to be used to share the state across all the components in your app. But the problem is, when companies start using redux, they tend to store all their API state (which ‘might’ need to be shared in the future) using redux. This makes sense for a project where many people are going to be working — and you want to have one consistent way of writing code. However, it increases the size of the code and reduces its maintainability drastically.
Although they have introduced redux-toolkit and that does clean up the code a bit — it still doesn’t compare to many other libraries out there that help do the job much better. Here is the go-to stack we use at My Next Developer:
React-Query: I use react-query to manage all the API states in my apps. It internally manages loading, error and success states for all API calls, caches results to avoid re-fetching and makes it super easy to access the data across multiple components. This is a beautiful tool — once you use it, I promise you won’t go back. Simple documentation and a hooks-based implementation make it very clean. Here’s a popular YouTube playlist of React Query Tutorial for Beginners by Codevolution to learn react-query.
Context: There are very few cases where we have a non-API-based global state that needs to be shared across multiple components. In those few cases, context is the easiest to implement. It also doesn’t unnecessarily add another library to the mix.
Do you disagree? I’d love to hear your thoughts!