When creating a React application, eventually we will come across a point where we need to pass some data from one component to another. And what is the simplest way to do this? Passing it as a prop to the children that require it. It’s simple and efficient. But what if I need this data in a component that is hidden deep inside many other components? Or maybe I need it in every single component? Here is when state management comes into play. When I was learning to code, the most obvious answer to how to handle state management was “use Redux”. It became so popular that it was used by almost everyone, sometimes without really understanding why. Fortunately, when React Context API was introduced, this trend slowly started to change.
But first things first... Let's start from the beginning.
Outline
- What is state management?
- Managing state with Redux
- Managing state with Context API
- Redux vs Context API. Which one to choose?
What is state management?
In simple words, state management is a logic of keeping and updating data displayed on the front-end. It can tell you if the user is authenticated or not, if the theme is set to dark or light, or even if this little radio button in the settings should be switched on or off. It synchronizes the data across all pages and often communicates with the backend.
Managing state with Redux
From my point of view, managing the state with Redux is much more complicated than it should be. For developers that are just starting to learn, it can be a real blocker as it introduces a lot of new concepts. There are actions, reducers, dispatchers, and if you want to use it asynchronically, then you have to additionally use redux-thunk or redux-saga. The amount of boilerplate here is real.
But what it really is and how to implement it? Redux is not built in React, which causes the ultimate size of the package to increase. It’s an open-source JavaScript library, which you have to install and prepare in a specific way.
Let’s use a real example. We will create a store that keeps the information about the theme used in the application. We have an option to set it as “light”, “dark” or “retro”. First, we have to create a store and a provider of it so it becomes accessible in the app:
Then, the reducer, which is responsible for how the application changes, contains steps for updating the global state and returns updated state based on the action:
We also need to create the mentioned action that is dispatched to call the reducer function:
And finally, we can create the component that will be connected to Redux:
There are a lot of things that need to be done and, as you could see, there are two additional functions at the end. mapStateToProps determines which data is injected into the component and mapDispatchToProps does the same with actions. Everything needs to be connected together to work properly.
With all of this logic, it can be misleading for beginners, but on the other hand, it's easy to test and debug, allowing us to log all states and actions. The given structure can also be treated as an advantage - experienced programmers can easily switch from one project to another.
Now, how would it look in the Context API?
Managing state with Context API
Context API has been introduced in React v16.3 and as the documentation states, “Context provides a way to pass data through the component tree without having to pass props down manually at every level”. This is something that we have to keep in mind while using Context API. Unlike Redux, components that are going to be used with Context, have to be somewhere inside the provider, so they can be included in the tree. How does it look in practice? Let’s have a look, using the same theme example.At first, we have to create a provider:
...and simply import it in the App component:
To use it inside the component, we have to use useContext function:import React, { useContext } from "react";
And that’s it! Simpler to understand, quicker, and with less code written. Despite all those pros, there is also one thing that we have to remember: Context is recommended for values that don't change very often. It doesn't mean it won't work for others. Of course, it will, but it doesn't let us subscribe to a part of the context value (or some memoized selector) without fully re-rendering. The whole provider tree is traversed for context consumers on each update, it doesn't “remember” the exact list like Redux normally does (Redux will ensure that the component only re-renders when a specific object in the store changes). The good news is, this problem can be solved by using several providers using memoization.
Redux vs Context API. Which one to choose?
The best way to sum up the comparison between Redux and Context API is take a look at the table of pros and cons of both of them:
Redux pros:
- Useful in big applications with high-frequency state updates
- Very popular - a lot of problems can be easily solved by the community
- Easy to test and debug
- Apps using Redux have similar infrastructure
- Eliminates unnecessary re-renders
Redux cons:
- Not built-in React, which increases bundle size
- Has huge boilerplate, much more configuration
- May be misleading for beginners due to a lot of hidden logic
- Requires middleware for async tasks
Context API pros:
- Resourceful and practical in small applications with minimal state changes
- Easy to understand and handle even for beginners
- Minimal configuration, only for creating the context
- Well documented
- Can use a lot of local contexts to handle separate logic tasks
- Can be easily used with async tasks
- Out-of-the-box, which leads to smaller package and better project maintenance
Context API cons:
- Not designed to use with frequently refreshed or changed data
- Could be more difficult to maintain in complex apps, especially if we have custom solutions and helpers
From my perspective, Context is much more comfortable to use than Redux. We used it in our two last projects and I can tell it is a great replacement for Redux that works very well and is simple to understand. We don’t have boilerplate code, don’t have to install any additional packages, even for async actions. Everything is ready to use right from the beginning. On the other hand, experienced software developers might still want to stick to Redux. Sometimes for more complex applications with more developers, it can be easier to get started with due to clear architecture that everyone knows and follows.
So which one should you use? Well, it’s still up to you. You have to estimate the size of your app, the experience and likes of your team, and define which global data needs refreshing and how often. Rewriting Context to Redux could be really hard, so this decision must be made at the beginning of your work.