DEV Community

Cover image for Why I Stopped Using Redux
Gabriel Abud
Gabriel Abud

Posted on • Edited on

Why I Stopped Using Redux

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 (100)

Collapse
 
chamsddine profile image
Bouzaine Chamsddine

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 .

Collapse
 
serhiiohorodnyk profile image
Serhii Ohorodnyk

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!

Collapse
 
jochri3 profile image
Christian Lisangola

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🤗!

Collapse
 
brianl profile image
Brian Lim

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.

Collapse
 
captaincodeman profile image
Simon Green • Edited

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...

Collapse
 
stephyswe profile image
Stephanie • Edited

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.

Collapse
 
g_abud profile image
Gabriel Abud

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.

Thread Thread
 
mattpocockuk profile image
Matt Pocock

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.

Thread Thread
 
theodesp profile image
Theofanis Despoudis

That implies that you know how to create correct state machines...

Collapse
 
kravemir profile image
Miroslav Kravec • Edited

one time i think redux is overkill is if you are working on small projects ... to build a scalable and maintainable application we need a centralized state

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.

Collapse
 
louiszen profile image
Louiszen

Once you use MobX, you will understand Redux is actually a garbage.

Collapse
 
farazamiruddin profile image
faraz ahmad

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 and useReducer, along with context. It's been fantastic.

If you're interested, you can check out my pattern for React Context

Collapse
 
g_abud profile image
Gabriel Abud

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.

Collapse
 
vitya profile image
Victor Kasap

I have one problem here re-rendering 100500 components when using the context? =)))

Collapse
 
caiangums profile image
Ilê Caian

TL;DR: If you are starting a project from scratch, consider not typing

$ yarn add redux react-redux
Enter fullscreen mode Exit fullscreen mode

Until it's necessary.

How to discover if it's necessary? For a quick discover, ask some simple questions:

  • does it have more than just one page?
  • does it have more than 1 form?
  • does it need to fetch tons of data?

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 ❤️

Collapse
 
ug02fast profile image
Arthur Zhuk

I'm using a custom caching solution like react-query at scale pleasantly. Redux is the wrong choice.

Collapse
 
caiangums profile image
Ilê Caian

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!

Thread Thread
 
michaelbushe profile image
michaelbushe

Redux is never the best choice. :)

Collapse
 
yashiroquy profile image
UMR • Edited

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.

Collapse
 
khelnagar profile image
khelnagar

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?

Collapse
 
samwightt profile image
Sam Wight

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 or useState 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.

Collapse
 
caiangums profile image
Ilê Caian

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! 😄

Thread Thread
 
khelnagar profile image
khelnagar • Edited

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?

Thread Thread
 
caiangums profile image
Ilê Caian

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;

Collapse
 
aralroca profile image
Aral Roca • Edited

If the answers are "YES", consider to use Teaful github.com/teafuljs/teaful 🤗

 
g_abud profile image
Gabriel Abud

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.

Collapse
 
bhatvikrant profile image
Vikrant Bhat

I agree, redux can be an overkill sometimes.

Collapse
 
rad_val_ profile image
Valentin Radu • Edited

you will find that on the vast majority of projects, Redux is overkill

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.

Collapse
 
g_abud profile image
Gabriel Abud • Edited

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.

Collapse
 
rad_val_ profile image
Valentin Radu

Got it and I agree: it's not generally bad. It's not good either tho 😅 And has never been, regardless of project size.

we end up shoving everything in a global store and recreating a cache from scratch

👍🏽 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.

Collapse
 
macsikora profile image
Pragmatic Maciej

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.

Collapse
 
g_abud profile image
Gabriel Abud

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.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
g_abud profile image
Gabriel Abud

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.

Collapse
 
yashiroquy profile image
UMR • Edited

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

Collapse
 
samkhan27 profile image
Sam Khan

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.

Collapse
 
vlence profile image
Victor Basumatary

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.

That's literally the definition of a frontend - a dumb interface. Frontends should never do anything with data besides display it, and collect it to send it off to the backend (think forms). It's interesting how the industry went from writing web applications using technologies like PHP, which in my opinion is so much easier to do and reason about, to making websites with React, Angular, etc. and all of that just to reinvent the wheel - like state management.

State management is a non-issue if your website's HTML is generated at the server side, because that's where the database is.

Collapse
 
michaelbushe profile image
michaelbushe

I think there is a lot of opportunity in that "never" space. Clients, even phones, today are more powerful than servers of not long ago. Why pay google or amazon for CPU when you can get your user's CPU free and provide them a better, faster service? I'm not the only one who things so, sheck out AWS GreenGrass. docs.amazonaws.cn/en_us/greengrass...

Collapse
 
vlence profile image
Victor Basumatary • Edited

Sure I suppose so but in my experience allowing yourself to process data both on the frontend and the backend only introduces unnecessary problems.

I think the most glaring problem is that you need to ask yourself where data should be processed whenever there is a need to process data. Consider a website which stores timestamps in its database. More often than not you'd like to show your users a nice, clean date string instead of a timestamp. Where should that formatting happen? That is data processing too after all. Should it be done client side? Server side? Instead of dealing with that dilemma you might as well process the data where it is - the server side, the backend. In my example of timestamps I like to tell the database in the query itself how I want my date to look like.

Yes data processing requires CPU but how much CPU do you really need anyway? And we're not getting the user's CPU for free. There are lots of people who don't have powerful phones and every millisecond spent in processing data shows. If you really need that extra CPU for data processing (What are you doing anyway? Image resizing? Video compression?) then consider using queues and queue workers.

Since the original discussion is about websites we should talk about JavaScript. You need JavaScript to do data processing client side. You have no guarantee JavaScript will be available client side. Here are some reasons why:

  • User disabled JavaScript
  • JavaScript could not be downloaded
  • User agent may not support JavaScript
  • User agent may not support the version of JavaScript downloaded

And as for Greengrass, this is what the docs say:

AWS IoT Greengrass is software that extends cloud capabilities to local devices. This enables devices to collect and analyze data closer to the source of information, react autonomously to local events, and communicate securely with each other on local networks. Local devices can also communicate securely with AWS IoT Core and export IoT data to the AWS Cloud. AWS IoT Greengrass developers can use AWS Lambda functions and prebuilt connectors to create serverless applications that are deployed to devices for local execution.

I've never done IoT, let alone use Greengrass, but this to me clearly reads as a backend-frontend setup where the lambda functions are the backend and the device sensors or whatever is collecting the data is the frontend. Even here you're not doing the data processing on the frontend.