Redux was a revolutionary technology in the React ecosystem. It enabled us to have a global store with immutable data and fixed the issue of prop-drilling in our component tree. For sharing immutable data across an application, it continues to be an excellent tool that scales really well.
But why do we need a global store in the first place? Are our frontend applications really that complex or are we trying to do too much with Redux?
The Problem with Single Page Applications
The advent of Single Page Applications (SPAs) such as React, brought about a lot of changes to how we develop web applications. Separating our backend from our frontend code allowed us to specialize and separate out concerns. It also introduced a lot of complexity, namely around state.
Fetching data asynchronously now meant that the data had to live in two places: the frontend and the backend. We have to think about how best to store that data globally so it's available to all of our components, while maintaining a cache of the data to reduce network latency. A big part of frontend development now becomes burdened with how to maintain our global store without suffering from state bugs, data denormalization, and stale data.
Redux is not a Cache
The main problem most of us get into when using Redux and similar state management libraries is that we treat it as a cache for our backend state. We fetch data, add it to our store with a reducer/action, and refetch it periodically to make sure it's up to date. We are making Redux do too much and using it as a catch-all solution to our problems.
One important thing to remember is that our frontend and backend state are never really in sync, at best we can create a mirage that they are. This is one of the downsides of the client-server model and why we need a cache in the first place. Caching and maintaining state in sync is immensely complex however, so we shouldn't be recreating this backend state from the ground up like Redux encourages us to.
The line between backend and frontend responsibility quickly becomes blurred when we start to recreate our database on the frontend. As frontend developers, we shouldn't need to have a thorough knowledge of tables and their relationships in order to create a simple UI. Nor should we have to know how best to normalize our data. That responsibility should fall on the people designing the tables themselves - the backend developers. Backend developers can then provide an abstraction for the frontend developers in the form of a documented API.
There are now a myriad of libraries (redux-observable, redux-saga, and redux-thunk to name a few) built around Redux to help us manage data from the backend, each adding a layer of complexity to an already boilerplate-heavy library. I believe most of these miss the mark. Sometimes we need to take a step back before taking a step forward.
What if we stop trying to manage our backend state in our frontend code and instead treat it like a cache that just needs to be updated periodically? By treating our frontends like simple display layers that read from a cache, our code becomes significantly easier to work with and more accessible to pure frontend developers. We get all of the benefits of separating out concerns without most of the downsides of building SPAs.
A Simpler Approach to Backend State
There are a couple of libraries that I believe are a huge improvement over using Redux (or similar state management library) for storing backend state.
React Query
I've been using React Query for a few months in most of my personal and work projects. It's a library with a very simple API and a couple of hooks to manage queries (fetching data) and mutations (changing data).
Since using React Query, not only am I more productive but I end up writing 10x less boilerplate code than I would have with Redux. I find it easier to focus on the UI/UX of my frontend applications without having to keep the whole backend state in my head.
To compare this library to Redux, it helps to see an example of the two methods in code. I've implemented a simple TODO list fetched from the server with both methods, using vanilla JS, React Hooks, and axios.
First, the Redux implementation:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from 'axios';
const SET_TODOS = "SET_TODOS";
export const rootReducer = (state = { todos: [] }, action) => {
switch (action.type) {
case SET_TODOS:
return { ...state, todos: action.payload };
default:
return state;
}
};
export const App = () => {
const todos = useSelector((state) => state.todos);
const dispatch = useDispatch();
useEffect(() => {
const fetchPosts = async () => {
const { data } = await axios.get("/api/todos");
dispatch({
type: SET_TODOS,
payload: data}
);
};
fetchPosts();
}, []);
return (
<ul>{todos.length > 0 && todos.map((todo) => <li>{todo.text}</li>)}</ul>
);
};
Note that this doesn't even begin to handle refetching, caching, and invalidation. This simply loads the data and stores it in your global store on load.
Here's the same example implemented with React Query:
import React from "react";
import { useQuery } from "react-query";
import axios from "axios";
const fetchTodos = () => {
const { data } = axios.get("/api/todos");
return data;
};
const App = () => {
const { data } = useQuery("todos", fetchTodos);
return data ? (
<ul>{data.length > 0 && data.map((todo) => <li>{todo.text}</li>)}</ul>
) : null;
};
By default this examples includes data refetching, caching, and stale invalidation with pretty sensible defaults. You can set the caching configuration at the global level and then forget about it - in general it will do what you expect. For more on how this works under the hood, check out the React Query docs. There are a ton of configuration options available to you, this only begins to scratch the surface.
Anywhere you need this data, you can now use the useQuery hook with the unique key you set (in this case "todos"
) and the async call to use to fetch the data. As long as the function is asynchronous, the implementation doesn't matter - you could just as easily use the Fetch API instead of Axios.
For changing our backend state, React Query provides the useMutation hook.
I've also written a curated list of React Query resources that you can find here.
SWR
SWR is conceptually almost identical to React Query. React Query and SWR were developed around the same time and both influenced each other in positive ways. There is also a thorough comparison between these two libraries in the react-query docs.
Like React Query, SWR also has really readable documentation. For the most part, you can't go wrong with either library. Regardless of what ends up becoming the norm in the near future, it will be much easier to refactor from that than the equivalent Redux mess.
Apollo Client
SWR and React Query focus on REST APIs, but if you need something like this for GraphQL the leading contender is Apollo Client. You'll be pleased to learn that the syntax is almost identical to React Query.
What About Frontend State?
Once you start using one of these libraries, you will find that on the vast majority of projects, Redux is overkill. When the data fetching/caching part of your app is taken care of, there is very little global state for you to handle on the frontend. What little amount is left can be handled using Context or useContext + useReducer to make your own pseudo-Redux.
Or better yet, use React's built in state for your simple frontend state. There is nothing inherently wrong with that.
// clean, beautiful, and simple
const [state, setState] = useState();
Let's embrace the separation of backend from frontend more fully instead of remaining in this ambiguous in-between state. These up and coming libraries represent a shift in how we manage state in single page applications and are a big step in the right direction. I'm excited to see where they lead the React community.
Top comments (98)
React-Query and context api can never replace redux (they are not the same thing ).and he is not overkill , the one time i think redux is overkill is if you are working on small projects , you can argue that we can use other state management solutions like xstate maybe but to build a scalable and maintainable application we need a centralized state .
That is very true - they are not the same things. But that is exactly the point of this article: redux is a global state management library, while react-query (or SWR) are cache management libraries. And the reality today is that most web applications (I would dare to say more than 95%) do not need global state management at scale, they need sophisticated caching library, because "single point of truth" is not in redux state - it's in the DB, coming from your api.
And I would argue about following "to build a scalable and maintainable application we need a centralized state" - I've refactored so many relatively complex react applications away from redux and they all became a lot more predictable and thus more scaleable and easier to maintain (not to mention that number of lines became 5-10 times less).
So if you have a source code example of an app that needs redux to be maintainable - I'd be happy to check it out!
I totally Agree with you.I've used redux for many year, and also redux-toolkit that has become the set of tools that is built by the redux team and is now the official library that the teams recommends. I agree that it has solved many redux problems in terme of boilerplate, complexe logic, etc..And it's more beginner friendly that redux.But, since last year, i've started using react-query.
When people say that redux and react-query don't try to solve the same problem, i don't really at 100%.Today redux is mainly used for trying to manager the server state if even it can be used for something else(for which the context API would be sufficient).What i really love is the way it's implements the concept of "single source of truth", cuz in the redux word, the "single source of truth" is just an plain JS object sitting in the client application, so to try make your redux store in sync with the API, they are many other solutions to plug on or reimplement some logic manually to make your redux logic more complex and less understandable.That's where react-query come in action, and i really believe that it will "kill" for "new projets" in some years, because of the concept of how it implements the "single source of truth" with your collection bound and in sync with the updated server state and for any other mechanism , no need to write complex code because everything you need you get if with just some few configurations.
My English is actually very bad, please understand that i'm French🤗!
Redux being "overkill" doesn't have much to do with "scale" (that's a misconception) but the requirements
medium.com/@dan_abramov/you-might-...
The man himself
If you don't need any of those requirements listed, using redux could be an exercise in technical supremacy and machoism. Or perhaps premature optimization.
There's also many other non-technical requirements for using redux that I won't speak to. For example, it makes interviewing or filtering easier. Yes redux, especially a lot of redux? You probably have enough frontend. No redux? You probably don't. If you work somewhere where everyone has a Ph.D. and skill is not an issue because everyone learned Redux in four hours then maybe everyone is excited and will use "centralized state". But if you don't, if you work somewhere with enterprise software where everyone wants to get home on time not work overtime and most important not spend hours and hours writing boilerplate (or engineering solutions to avoid boilerplate, a fraught business itself), then redux could be a curse.
Redux is intended to solve a problem if you don't have that problem you don't need it.
I honestly think that blog post is a bit of a cop-out. The reality is that what Redux does is great, but it does it all in such a complicated way that it makes things difficult, so difficult that it may not be worth it unless you're doing a significant problem.
But you can do all that Redux does in a much simpler way so you don't have to write as much code and do it all with significantly fewer bytes (more the ecosystem of extras that you end up including than the core lib itself)
More in-depth thoughts (ranting) about Redux an an alternative I came up with:
captaincodeman.com/2020/05/31/intr...
On one router page we make 15 action based api calls . How you handle that w/o redux ?
You also have persist state in redux.
At a certain complexity it may make sense to start using Redux. I am not against Redux as a tool (I say that in the first paragraph) and I think its design is excellent, I'm more against what it encourages us to do.
Having said that you can easily do that many calls with React Query and SWR and have them be cached so they are not refetched every time. You simply use a hook in the components that are needed and don't have to worry about managing your own global store.
I'd argue that at a low level of complexity, use useReducer, useState and useContext. At a high level of complexity, use XState/useContext. XState handles complexity much better than Redux.
That implies that you know how to create correct state machines...
Redux is a very wrong tool for any front-end (dumb client/interface) of information systems, where the back-end is the single source of truth - the centralized state.
Even user's settings are stored in BE, and therefore should be treated as network resource. I couldn't imagine working with information system, which wouldn't remember my settings across different sessions / browsers / computers. Rather, not have any user settings at all, then.
However, redux might be a good choice for rich applications, where the front-end is practically a full-featured desktop application, and the back-end serves as mere storage for BLOBs (with metadata).
My opinion is based on experience with a large / enterprise application using Apollo GraphQL. And, that I've just migrated my pet project from custom-crafted very-sub-optimal redux caching solution to react-query.
Once you use MobX, you will understand Redux is actually a garbage.
I agree with your statements about Redux. The problem honestly is the way it's used.
If used to only store state that is actually global, it's fine. However, I think most of the time a lot of us like to default to throwing things into the global store to avoid prop drilling.
You can simply use context these days to avoid prop drilling. We've been replacing our application at work from Redux and moving towards using
useState
anduseReducer
, along with context. It's been fantastic.If you're interested, you can check out my pattern for React Context
I've been doing the same thing on all of my projects, moving to pure useReducer, useContext, and useState. I am sure there are legit cases for Redux but I think it's for a select few, really complex applications.
If you haven't tried react-query or similar library, I'd encourage you to try them out as well. It handles the data layer really cleanly and independently from context.
I have one problem here re-rendering 100500 components when using the context? =)))
TL;DR: If you are starting a project from scratch, consider not typing
Until it's necessary.
How to discover if it's necessary? For a quick discover, ask some simple questions:
If the answers are "No", consider using Context API or React-Query.
Remember: You are free to use anything you want! Be happy!
Stay home ❤️
I'm using a custom caching solution like react-query at scale pleasantly. Redux is the wrong choice.
I would never say: Redux is the wrong choice always. Sometimes you need what it offers! Sometimes, Redux is really a big DON'T.
I'm happy that you found a perfect tool for your projects!
Redux is never the best choice. :)
I built a SPA with more than one routes, and fetch tons of data. After the project started become big, I thought maybe I should try learning Redux. After reading the docs about Redux, I decided to stick with useReducer and context because I understand nothing.
If my answer is yes to all, and I am actually developing a social network like app (somehow FB like), can context API and React built-in state management suffice?
Probably not, no. The main tools that Redux provides are middleware and dev tools. With middleware, you can integrate things like thunks into your store, making it incredibly easy to fetch data in your app. Dev tools make it very easy to actually see what's going on in your store, something
useReducer
oruseState
can't let you do as easily. If you're building any sort of complex application, I'd recommend using Redux at the start to avoid the issues that pop up later if you don't.It depends! For big amount of data, Context API tend to be slow. Even if you want to go with Redux, consider checking MobX lib and thinking what you really need. There's no silver bullet and sometimes your thrive for using one specific tool to all your needs can drive you to unpleasant places! 😄
Thank you for your reply.
Sure it depends that’s why I am still thinking. I am not really aware yet of how big data would be or which parts (all or some?)of the app would need the global state so that I can define it is really big, to decide whether to dump Context/hooks.
I more inclined into avoiding Redux as I can see that my current challenges are just prop drilling and updating state globally from anywhere in comp tree, where I see built-in React is enough.
Am I missing future challenges that Redux would solve down the line, and I am not aware of as not grown big yet?
You can always change from one to another tool, if you need to do so. In my personal opinion we lose too much time advocating for one or another tool instead of experiment and create Proof of Concepts.
There are things that we'll only learn by experience and time. If Context API is ok and you feel that this tool attend all your needs, it's ok to maintain! Just don't be blind to another tools and avoid learning just because someone told you to :smile;
If the answers are "YES", consider to use Teaful github.com/teafuljs/teaful 🤗
If you are making a SPA, you have no assurance that they are in sync though, even in this instantaneous situation. By the time you load that array someone could have edited that data in the backend already. This is important to keep in mind so you don't make any assumptions that later become very real bugs. So if you have an application where there aren't a ton of live edits, this may not matter as much, and you're right about that. But backend/frontend are never really in sync and making this assumption might lead to weird UI/UX behavior down the line, as your app grows and you get more users editing the same data.
I agree, redux can be an overkill sometimes.
I've heard this statement 100 times and I have to object: it's not overkill, it's simply not the best tool for managing state in a React app for the reasons you mentioned and others as well (e.g. the extra effort/complexity/library for async operations, the extra setup for statically typed actions, etc.)
No matter if we're talking about 100 lines of code or 100,000.
On the other hand, an Apollo-based solution or a React Query one coupled with a BFF brings us closer to what should be our goal in the first place: an architecture that allows seamless state management between the server and the client.
Yeah my point is not that Redux is bad per se, more that the way we generally use it and other state management libraries is bad. If you have a lot of global state to manage that is truly frontend state, then it may be worth it to use Redux.
What I meant is that we're generally not using it correctly and it often leads to bad patterns where we end up shoving everything in a global store and recreating a cache from scratch.
Got it and I agree: it's not generally bad. It's not good either tho 😅 And has never been, regardless of project size.
👍🏽 I guess, independently of whether we use Redux or not, we'll end up creating some sort of cache-like system, because in the end, that's what out client side state is in a way: a cache for the backend state. So yeah, better if we don't start from scratch every time.
Good article. My main issue with Redux was that no matter how I tried to make the state well architected and normalized, I have always ended with a mess. The whole normalization is a big pain in the ass which really creates more problems than it solves.
Also this common talking - don't put into Redux private component state, ends always by - nobody knows what to put to Redux, so let's put everything and we are done with this question.
I am also done with Redux, it's never worked fine. Reducers are great, but that we have in useReducer. So bye bye Redux.
Well put, this is exactly my experience too. No one is trained on how to use Redux correctly and it quickly devolves into a mess. You have to have very stringent code reviews/practices to keep it maintainable and in most situations it feels like over-engineering.
I think this is fine, kind of not the point of the article but I agree. Knowing when to use technologies and when they are overkill is a big part of being a good software engineer. If you are making static websites and don't need fancy Javascript capabilities, React might be overkill.
I set up a simple project with Django and vanilla JavaScript few weeks before quitting the job, and a newly hired senior developer decided to re-setup the project. Even though I quit to focus on backend and devops, I still decided to read his code to see if I could learn anything. He set up the frontend with ReactJS, Redux, saga. Nonetheless, he used class component instead of hook. His code looks like a mess to me, and I am glad I quit being full-stack dev
Redux is not a framework or a technology. It's an architecture pattern and a damn good one at that. So much so, that facebook includes the useReducer hook in React's top level APIs.
I agree that there is a tendency in the community to use redux before it becomes necessary and to do so without understanding the benefits it provides. However, to suggest SWR and and React Query as an alternative to redux is harmful and MISLEADING. You could pair them with redux to form a pretty effective stack.
I have shipped complex production apps with and without using redux. It usually depends on the use case, app's requirements and complexity in the application's state management. I've found that in applications where redux is not a good fit, apollo client is. This is because apollo client utilizes a normalized state in to form of apollo cache. Apollo client sort of abstracts out the custom plumbing you would otherwise have to write if you were using redux and if the out of the box features in apollo client are enough for you, it may just be enough. Some of those apps still benefit from redux like patterns though, like using the useReducer hook in complex components.
First off, I love this article to the bone. As a point of polish, I think it's worth clarifying here that SWR did not inspire React Query. twitter.com/tannerlinsley/status/1...
Ah okay, in your old README you had a section that I thought said that. But maybe I misunderstood and you meant some of the ideas were influenced by SWR (and vise versa) . Will change it now!
Thanks!
Hello