When I first learned how to use Redux, it was a bit overwhelming keeping up with the different files, functions, types, actions, and reducers necessary to make it work. After I got the hang of it, using it felt almost like second nature — it’s highly structured, it’s easy to tell where bugs are coming from, and there’s a repeatable pattern when building out features. But when I learned about the React Context API I was baffled by how simple it was to manage state across components, and I wondered why I had been using Redux at all.
I set out to investigate the reasons why Redux, despite being more bloated (sorry old friend) and complicated to use, still has its place in any developer’s toolbox.
The actual implementation of Redux has more components, such as dispatch and thunk middleware if you have asynchronous side effects, but I will keep it simple so we can move on to comparing Redux and Context. Here is a helpful gif from the docs that helps paint a picture of how state gets updated using Redux.
Moving on, let’s define React’s Context API. You can find the Context docs here. Similar to Redux, Context is used when you want to share state across many components at different levels in your component tree without passing it as props through every component. Context is a part of React, so you will not need to install a third-party library or deal with boilerplate as is the case with Redux. In order to use Context you need three basic elements:
Context is used when you want to share state across many components at different levels in your component tree without passing it as props through every component.
At first glance, using Context seems like a pretty sweet deal, and in many cases it is! It’s built into React, it’s easy to learn and use, it feels lightweight compared to Redux, and you don’t have to set up all those reducers, actions, types, and so on. If you’re building a medium-sized personal project, Context could really be the perfect solution for you.
However, Redux still has a thriving community around it for a reason. That in itself can come in pretty handy — odds are someone has already documented and solved that Redux problem you’re having! If you are working in an enterprise setting with a large codebase and multiple engineers, Redux starts to look a lot better. It forces you to be organized and follow certain patterns. Even if you didn’t write the code you can follow the trusty Redux trail, from action to type, to dispatch, to reducer, and find what’s going wrong because of Redux’s code structure.
Redux has an edge over Context when it comes to debugging, not only because of predictable code structure, but because of tools like Redux Devtools and other plugins that provide powerful insights into how state changed over time. Redux also allows the use of middleware, which incorporates third-party extensions into your Redux flow. Because Redux is a library, it is more powerful and full of features that Context simply doesn’t have.
The last and perhaps the most important reason why currently Redux is still very viable is that Context is not recommended for use in applications that frequently update. Redux is very efficient when it comes to eliminating unnecessary re-renders, but out of the box, Context can become very inefficient and cause a lot of unnecessary re-renders if your app is receiving frequent updates. The reason is that all components that are context consumers will update when any value in a context object updates, regardless of whether the change was relevant to that component. There are workarounds to this, such as using memoization to remember previous values to prevent re-renders, but Context alone doesn’t deal with the issue.
When I started this article I didn’t expect this to become a love letter to Redux, but I definitely have more appreciation for everything it can do. It’s important to note that neither is “better”, they are merely different tools that have different use cases, and as long as you understand your project and its needs, it will become clear which is best for the job.