Redux as a global state management solution has been around for a while now and has become almost the go-to way of managing state in React applications. Though Redux works with JS apps, the focus of this post will be React apps.
I’ve seen people add Redux to their dependencies without even thinking about it, and I’ve been one of those people. A small disclaimer: This is no rant about Redux being good or bad, of course, Redux is great that’s why so many people have been using it for years now. The aim of this blog is instead to share my opinions on the use of Redux and where it makes sense and more importantly where it doesn’t.
React’s Context API has been around for some time now and it is good, useReducer is great as well, but that doesn’t make Redux obsolete. Redux still makes sense to me. Let’s not make size be the parameter for using (or not using) Redux. It is not about the size of the application, it is about the state. That is also where the pain begins. In large applications with multiple developers working on the same codebase, it is easy to abuse Redux. You are just a bad choice away and then everyone starts pushing anything and everything into the Redux store. I’ve been one of those people, again.
“Hey! What do you mean by abusing Redux? It is meant to be a global data store right?”
Yes, it is meant to be a global data store but the term global data store is often translated as a state to hold every state, value, data, and signal. That is wrong, and it is a slippery slope, it goes from 0 to 100 quite fast. Soon enough you will find yourself working on an application with an absolutely messed up global state. Where when a new guy onboards, he doesn’t know which reducer to use data from because there are multiple copies or derived states.
People often get used to the fact that they’ve to make changes in 3 files whenever something changes – Why? That’s a pain, we’ve got accustomed to and as the size of application or scope increases this only gets worse. Every change becomes incrementally difficult because you don’t want to break existing things and you end up abusing Redux further.
When that stage comes, we often like to blame React and Redux for the meaty boilerplate it asks of you to write.
“It’s not my problem or my code’s problem, that’s just how Redux is…”
Yes and No. Redux definitely asks you to write some boilerplate code, but that’s not a bad deal until you overuse it. As often said, nothing good comes easy, and the same applies to Redux. You have to follow a certain way that involves:
– Keeping the application state as plain objects. (Store)
– Maintaining changes in the system in the form of plain objects. (Actions)
– Describe logic to handle state changes in terms of objects. (Reducers)
In my opinion, that’s not a very easy pattern to follow and introduce in your applications. The steepness of this curve should deter the abuse of Redux and should make you think before opting for Redux.
React gives you a local state. Let’s not forget it and use it as much as possible before looking for ‘global’ solutions, because if you pay close attention, most data in your Redux store is actually just used by one or two components. ‘Global’ means much more than one or two components, right?
Redux for state
Right, that’s what Redux is for – maintaining your global state, but pay close attention and you will find values that are required by just a single component but you thought that someone might need them in future in some other component. Therefore, why not put in the effort to put this local data in Redux. That’s where we are often wrong. Because the chance of a future guy requiring the same data in some other component is really low and even if that happens, chances of duplication of data and derived Redux states are good. Over time, this practice of putting values unnecessarily in the Redux store becomes perfunctory. You eventually land in a big stinking mess of reducers and states where nobody wants to touch anything. They would rather create a new reducer, in the fear of causing regressions in, god knows which, component. I know proper code reviews and processes in place will not let the situation get that dire, that fast but definitely, the morning will come, when the realization strikes and all you are left with is an insurmountable tech debt of fixing state management in an existing code base with minimum regressions.
So heed my words – whenever you are thinking of going the Redux way with a state, give a good thought to it. Is that data really of ‘global’ use, are there other (non-immediate child) components which require that data? If the answer is yes, Redux-land is the home for that data.
Redux for data fetching
Redux mostly gets looped into the data fetching scene in the React world but, why? Why do you have to write an action reducer every time you want to make an API call? In huge applications, it might make sense, but for medium to small projects, it is an overkill. Redux is meant to store values/data which might be used by multiple components in your application, possibly on different component trees. Data fetching from an API to fill up a component isn’t really something that fits that definition. Why should data fetched from an API to be used by a single component, go through the store to the component in question?
Now, the first point against this line of thought would be:
We don’t want to pollute our components with data fetching logic…
I am all for clean and readable code, and readability principles demand that one should be able to comprehend what is happening in your code, as easily as possible (with an opening as little files as possible). Now keeping that in mind, I don’t think to have your data fetching logic specific to a component inside that component is polluting it. It is actually making your code readable and understandable as probably the most critical part of your component. The data it fetches and how it fetches it is quite conspicuous to the reader, isn’t it?
Moreover. I am not asking you to put your fetch calls inside your components just as they are, there are a lot of great abstractions out there, which are seemingly easy to use and do not take away the brevity and readability of your code.
Since Redux is a global store, we don’t have to fetch the data again…
Some of you might have this as an argument. Well, most of us make API calls to fill up our components whenever the component “mounts” and that data comes via Redux, right? So until you have proper data validation mechanisms to know that your Redux store needs to be repopulated with fresh data from an API call, you are going to make that API call on every “mount”, so how are we saving anything there? And if what you really want is ‘caching’ so that you can save on your API calls, why not go with solutions built for that, instead of trying to mold Redux into your cache?
There are a lot of brilliant libraries out there to help you tidy up your data fetching. SWR by the good folks at Zeit is one amazing utility to get started with. If you want to take it a notch up, you can consider going with react-query by Tanner Linsley both of these are mostly based around render time data fetching and provide great ways to optimize your data fetching operations.
Sometimes you might need event-based data fetching – fetching data when some particular ’event’ happens. For such cases, I authored a utility called react-str, which I’ve been using a lot by now and it makes things quite nice and concise.
I’ve been walking this path of avoiding Redux for data fetching for some time now, and I am quite happy with the results. Most performant and most maintainable lines of code are the ones not written, and avoiding Redux in your data fetching can save you a lot of time, code with almost no harm.
Redux for signaling
At times in our applications, we need to do something based on the occurrence of some event somewhere else in your code. For example, I want to change the text inside a header component whenever the user scrolls beyond a certain limit in another component. Usually, how we solve this is by maintaining a Redux state which is a boolean keeping track if the user scrolled to that limit or not. Then, we watch for that state value in our header, when it changes we change our header text. Sounds familiar? Or suppose you have a general confirmation modal which you might want to show at multiple places in your application, you can either call it at these many places or just put it on the parent component, toggling its visibility somehow. Again you’ll be maintaining a set of actions and a reducer to maintain modal visibility state, right? These cases might not be that frequent but these are not rare as well.
To reiterate, signaling flags or values like the one above are rarely ‘global’ and are required to be received by one or a few components at most. So why do we put these values in our so-called global store?
Maintaining an action, reducer and a single boolean value in Redux for signaling the occurrence of an event to another component seems overkill to me. So I wrote an event emitter. Which lets you fire events and handle them anywhere in your code, without indulging Redux.
Redux for future proof applications
In most cases, teams opt for Redux because some senior members on the team think they might need it in the future. And this often becomes a very serious mistake later on, because it is not easy to rollback on such decisions. You and possibly your team might be taking the future and especially Redux, as part of the React world, too seriously. As Dan Abramov. co-creator of Redux. has said:
It’s just one of the tools in your toolbox, an experiment gone wild.
Redux is just another library, albeit a great one. Use it when you identify the need for it, not just because it is such a major name in the React ecosystem.
And, if you are influenced and love the pattern Redux enforces, you can enforce it without Redux also, the pattern is called flux and you can read more about it here.
I think it is the right time to share some wisdom: Nobody knows the future, not even the seniors – all the time. The future depends on the present and the decisions we make today reflect tomorrow. It is okay to ask why, before adding anything to your bundle. Remember, as Front-end engineers, the user is King and every avoidable package you use, add to that bundle size, making it heavier and ultimately slower to load.
Maybe I am exaggerating things a bit, but this is not just about Redux. Every package you add on to your project, think, think, think! Is it really needed? And, how can you minimize the costs on bundle size? A few tens of kilobytes might not seem a lot, but on a mobile network in some far away, developing land, that might be the deciding factor if a user is able to use your application or not. That’s all that matters, right?
So, these are my thoughts on the current state (pun definitely intended) of Redux usage across React apps. The problems shared here, are the ones I have observed first hand on the projects that I’ve worked on. Also, the solutions shared are the ones we used/developed to get out of these problems. Are they perfect? No, nothing is, but they definitely seem better at the moment.
I hope this post gave some points worth your time on why you probably don’t need Redux. If you are going to start a new project soon, this might help in deciding if you really need it, or if you have an existing project with Redux, you can make your store quite lean and free your codebase of much boilerplate by employing these practices.
If the title seems familiar, it is derived (more unsolicited puns) from – You probably don’t need derived state – this excellent official React blog post by Brian Vaughn, if you haven’t read it, do check it out!
Through this post, I would like to thank Rishabh Gupta for the ideas and learnings shared. This would not have been complete without his guidance and help.