When to Use Context API vs Redux

If someone would ask me, what's the most important piece of any website. My answer will always be the same. It's the application state. After all, it's the state that is deciding what will users see.

In React, each component has its local state. This piece of state can be used to track what's happening inside the component. But when we want to track what's happening inside the application as a whole, local state is no longer enough. For these situations, we need to use a global state.

To pick global state management for React, we have a lot of options to choose from. For years, Redux seemed to be the most popular choice. But when Context API was introduced, developers started to quickly adopt it. In some scenarios, they started to replace Redux with Context. And with all of this happening, one big question started to arise. đź—˝

When to use context API vs Redux?

If you're only using Redux to avoid passing down props, you can replace it with Context API. Context is great for sharing trivial pieces of state between components. Redux is much more powerful and provides a set of handy features that Context doesn't have. It's great for managing centralized state and handling API requests.

Difference Between Context API and Redux

The main difference between these two libraries is that Redux deals with changes of the state in a centralized manner. On the other hand, Context deals with them as they happen on the component level. But to have a better idea about the difference between these 2 libraries, we have to look at each one separately.

Context API

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Context API is a fairly new concept in the world of React. Its main purpose is to share data between components without using props or actions. It is designed to share data that can be considered global for a tree of React components, such as theme, or preferred language. Context can significantly reduce the complexity of state management in your application. 🚀

It has 2 core concepts:

  • Provider
  • Consumer

The job of the provider is to define and track certain pieces of state. This state can be accessed by all the children nested inside Provider. These children are usually referred to as Consumers. Consumer is every component that is accessing or modifying the state from Context Provider.

Redux

Redux helps you write applications that behave consistently, run in different environments, and are easy to test. Centralizing your application's state and logic enables powerful capabilities like undo/redo, state persistence, and much more.

Redux is a JavaScript library that helps to manage data flow in a centralized manner. It stores the entire state of the application. This state can be accessed by any component without having to pass down props from one component to another. It has 3 core concepts:

  • Actions
  • Reducers
  • Store

Actions are events that send data to the Redux store. They can be triggered by user interaction or called directly by your application. Each action needs to have a unique type and payload associated with it. The example action can look like this. 👇

{ 
  type: "SIGN_IN",
  payload: {
    email: "[email protected]",
    password: "12345"
  }
}

Dispatching an action will trigger the reducer to run. A reducer is a function that takes the current state and based on the action it received, returns a new state.

All of this is happening inside the store. The store has one responsibility, that responsibility is to hold the application state. It is highly encouraged to keep only one store in any application that uses Redux.

Now that we understand how both Redux and Context works. It's time to look at the applications for both of them.

Context API Applications

Context is ideal to use for sharing global data such as current authenticated user, theme, or the user language.

Now, let's implement the context that will track theme property for our React application.

import React, { useContext, useState } from "react";

// Settings default values
// These well later be overwritten by specifying 'value'
const ThemeContext = React.createContext({
  theme: "light",
  setTheme: () => "",
});

const App = () => {
  const [theme, setTheme] = useState("light");

  return (
    // Wrapping App component with Theme provider
    // All the children can now access theme property
    // Additionaly, they can change the theme property
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Hello />
    </ThemeContext.Provider>
  );
};

// Consumer of the Theme context
// Checks the value of the theme and changes the color based on the theme 
const Hello = () => {
  const { theme } = useContext(ThemeContext);

  return (
    <h1 style={{ color: theme === "light" ? "black" : "white" }}>Hello đź‘‹</h1>
  );
};

As you can see in the example above, Context doesn't require a lot of setup and boilerplate code in order to work.

On top of that, it's included by default in React library, so you don't have to install any dependencies. đź‘Ś

Redux Applications

Redux is most commonly used in situations when:

  • Application has a large amount of state, needed in many components.
  • Application state is updated frequently.
  • The logic to update the application state is complex.

To better understand the ideal use case for Redux. Let's implement the piece of state that will track the list of application users.

const initialState = [];

const usersReducer = (state = initialState, action) => {
  switch (action.type) {
    case "SET_USERS":
      return action.payload;
    case "ADD_USER":
      return [...state, action.payload];
    case `EDIT_USER`:
      const newState = [...state];
      const index = newState.findIndex((item) => item.id === action.payload.id);
      newState[index] = action.payload;
      return newState;
    case "DELETE_USER":
      return state.filter((user) => item.id !== action.payload.id);
    default:
      return state;
  }
};

To initialize Redux, we need to wrap the whole App component inside the Redux Provider and initialize the store.

import { Provider } from "react-redux";

import userReducer from "./reducers/userReducer";

// Creating instance of a store
const store = createStore({
  users: userReducer,
});

const App = () => {
  // Setting the store instance
  return <Provider store={store}>...</Provider>;
};

export default App;

The last part is accessing the state, which can be done by connecting the component to a state.

import React from "react";
import { connect } from "react-redux";

const Users = ({ users }) => {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

const mapState = ({ users }) => ({
  users,
});

export default connect(mapState)(Users);

This was a very trivial example to demonstrate the power of Redux. As you can imagine, Redux can be used to handle much more complex states. After all, it was built for this purpose.

Redux is trying to simplify managing complicated states by providing us with a set of useful tools, like Redux Dev Tools, state persistence, asynchronous actions, etc...

Other State Management Tools

In this article, we tried to compare React Context API with Redux. But as you might be guessing, they aren't the only state management tools out there. In fact, there are plenty of other tools that treat state management in their own unique way. 🕺🏻

In this section, we're gonna mention some of them.

We're not encouraging you to use these tools, nor we're saying they are better than Redux or Context API. We just simply want to spread the word.

React-Query

React Query was specifically developed to handle state management around data fetching. It provides a lot of helpers that make data fetching a piece of cake.

To learn more, read the documentation here.

Recoil

Recoil is a fairly new state management tool developed by Facebook. It is still being actively developed. In Recoil, each piece of state is called an atom, and atoms can be combined with selectors. You can combine atoms and selectors to create data structures unique for your application.

You can learn more here.

MobX

MobX implements a class-based approach. And the whole idea behind MobX is to make state management “observable”. You can read more about MobX here.

Concluding Thoughts

State management is one thing that all web applications need. When we're deciding how to manage global state in React application. The commonly asked question is: when to use Context API vs Redux. It's important to understand how both Context API and Redux work. And it's also important to choose the correct tool for your use case.

In this article, we showcased basic examples of both Context and Redux. We mentioned ideal use cases for both these libraries. But most importantly, we answered the question of when to use Context API vs Redux. With this knowledge in your pocket, you can correctly decide when to use Context API vs Redux. đź‘Ť

20