Managing State In React Apps: Redux vs React Context

Patryk Grzyb
28 October 2021 · 7 min read

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.

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:

import React from "react";
import { Provider } from "react-redux";
import { createStore } from "redux";
import theme from "./reducer";
import Settings from "./settings";

const initialState = "light";
const store = createStore(theme, initialState);

export default function App() {
 return (
   <Provider store={store}>
     <Settings />
   </Provider>
 );
}

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:

const theme = (state = [], action) => {
 switch (action.type) {
   case "CHANGE_THEME":
     return {
       ...state,
       theme: action.theme,
     };
   default:
     return state;
 }
};
 
export default theme;

We also need to create the mentioned action that is dispatched to call the reducer function:

export const changeTheme = (theme) => ({
 type: "CHANGE_THEME",
 theme,
});

And finally, we can create the component that will be connected to Redux:

import React from "react";
import { connect } from "react-redux";
 
import { changeTheme } from "./action";
import ThemeButton from "./ThemeButton";
 
function Settings({ theme, changeTheme }) {
 const themes = ["light", "dark", "retro"];
 
 return (
   <div>
     <h1>Settings</h1>
 
     <h4>Change your theme (current: {theme})</h4>
     {themes.map((theme) => (
       <ThemeButton
         key={theme}
         style={theme}
         onClick={() => changeTheme(theme)}
       />
     ))}
   </div>
 );
}
 
const mapStateToProps = (state) => ({
 theme: state.theme,
});
 
const mapDispatchToProps = (dispatch) => ({
 changeTheme: (theme) => dispatch(changeTheme(theme)),
});
 
export default connect(mapStateToProps, mapDispatchToProps)(Settings);

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:

import React, { createContext, useState } from "react";
 
export const Context = createContext(null);
 
export default function Provider(props) {
 const [theme, setTheme] = useState("light");
 
 return (
   <Context.Provider
     value={{
       theme,
       changeTheme: (theme) => setTheme(theme),
     }}
   >
     {props.children}
   </Context.Provider>
 );
}

...and simply import it in the App component:

import React from "react";
import Provider from "./Provider";
import Settings from "./settings";
 
export default function App() {
 return (
   <Provider>
     <Settings />
   </Provider>
 );
}

To use it inside the component, we have to use useContext function: import React, { useContext } from "react";

import { Context } from "./Provider";
import ThemeButton from "./ThemeButton";
 
export default function Settings() {
 const themes = ["light", "dark", "retro"];
 const { theme, changeTheme } = useContext(Context);
 
 return (
   <div>
     <h1>Settings</h1>
 
     <h4>Change your theme (current: {theme})</h4>
     {themes.map((theme) => (
       <ThemeButton
         key={theme}
         style={theme}
         onClick={() => changeTheme(theme)}
       />
     ))}
   </div>
 );
}

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.

Join our team

Read also:

How To Create Your Own Loading Circle?

Legacy APIs and How To Integrate Them with Django, Celery, and A Lil' Bit of Tenacity

Flask vs Django

Share on
Related posts
9 Bespoke software development mistakes to avoid
SOFTWARE DEVELOPMENT

9 Bespoke software development mistakes to avoid

If making custom software was easy, everybody would build their own apps and there wouldn’t be a shortage of programmers. Unfortunately, the only thing that’s easy about software development is making…
9 min read
A11y: Web Accessibility Definition And Guidelines
SOFTWARE DEVELOPMENT

A11y: Web Accessibility Definition And Guidelines

Hundreds of millions of people use the internet on a daily basis. It can easily be overlooked, but we should always make sure your website is accessible to everyone, regardless of their disability…
7 min read

Tell us about your project

Get in touch and let’s build your project!