React Context API — A Replacement for Redux?

Convert a React App that uses Redux for State Management to use React’s New Context API

Rajat S
Bits and Pieces

--

React’s version 16.3.0 has brought many new concepts and features.

One such awesome feature is React Context API- a new, production-grade, efficient API that supports things like static type checking and deep updates.

In this post, I’ll show you how to convert a React app that uses Redux for state management to using the new Context API- and learn when to use each one.

  1. What is Context API?
  2. Converting from Redux to Context API
  3. When should you use which one?

In this post I’ll also be using Bit. It’s a great tool for sharing UI components, creating a visual catalog and making them available to easily discover and sync in different projects. It’s open source, so feel free to go ahead and try it :)

React Context API — What is it?

With release of React 16.3, React is urging us to migrate to the new Context API. If you search for Context in React’s docs, this is the formal definition.

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

In React, data is often passed from a parent to its child component as a prop.

This method has worked well in the past, but is not suitable for every kind of data. It will make things difficult later when moving components around. props even get passed down to child components which aren’t using the data.

Is Context API a new thing?

The truth is that Context has been a part of React for a really long time.

Remember the <Provider> tag that we used earlier in the Redux app? It is actually a part of the older React Context API. React Context API is even used by libraries such as react-redux, MobX-react, react-router, and glamorous.

So why is React Context API making such a big noise now?

React used to discourage developers from directly using it in their apps and it was only included on an experimental basis. Now, with official support + the ability to render props, Context brings us a delightful new experience.

How to use this new version of Context?

The new context API consists of three main steps:

  • React.createContext is passed the initial value. It returns an object with a Provider and a Consumer.
  • The Provider component is used higher in the tree and accepts a prop called value. This value can be anything!
  • The Consumer component is used anywhere below the Provider in the tree and accepts a prop called children and must be a function that can accept a value and return a JSX.

Let’s take an in-detail look at how to use React Context API by migrating the earlier app from Redux to React Context API.

Let’s build a Redux App and Convert it to Context API!

Building the Redux App

Redux version: https://bit.dev/geekrajat/dcsuperhero/src/preview

React Context API version: https://github.com/rajatgeekyants/superhero

Getting started with redux requires a certain learning curve, but here’s the gist of what you should know when suing Redux.

  • The state of the entire app is store in a single object tree within a single store.
  • To change the state, you need to trigger an action that describes what needs to happen.
  • You should never mutate the state directly, always return a new object to do so.

Let’s build a simple React App that uses Redux to manage its state.

Project Setup

First, let’s create a React project directory using create-react-app.

$ create-react-app dex

Once the directory is created, we need to install Redux as a dependency.

$ cd dex
$ yarn add redux

To bind Redux with React, we need to add another package called React-Redux as a dependency.

$ yarn add react-redux

With these dependencies installed, we can begin writing our code.

Code

Open the project directory in any code editor. I personally like to use VS Code.

Let’s start with our Redux Store. Store is where we will store our data and update it as someone uses the app. Create a file named reducers.js inside the src folder of your app and write the following inside it. You can also create a separate file named store.js and write the store there, but here I am going to write it in reducers.js.

We will create our Reducer in the same file. Below the Store declaration, write the following code:

Note: The hero.js file contains a long list of DC Superheroes. Here is a minimalistic version of it.

Next, we will write our Actions. In this app, our actions are synchronous. If our actions were asynchronous, we would have to write our actions in a different way. We would have use some more libraries like Redux-Thunk, Redux-Saga, and Redux-Pack to help Redux handle asynchronous functions.

Create a new file named actions.js and write the following code inside it:

With that taken care of, we need to hook our Actions to the App.

This is where React-Redux comes into play. It has a component called Provider that will make our Store available to the rest of the components.

Provider is used at the root level of your app. So, all the child components can have access to the Store without having to write the import statement in every file.

Provider uses the older Context API and an higher order component (HOC) called connect to access different properties and make them available to different child components of the app through props.

Rewrite the index.js file with this code:

We now need to use connect in the child component to access the data and “connect” with the Actions to be able to modify the data.

In your App.js file, replace the export statement with this:

export default connect(store => store, actions)(App);

With this, the connect function will now return a new component that will make the Store and Actions available to App as props.

Since I don’t have any need of internal component state or any lifecycle methods, I will write my App component as a stateless functional component.

This is sufficient for a basic Redux app, but obviously things can get more complicated as we scale our app.

If you now run yarn start or npm start, you should see something like this:

You can look at the entire source code of the Redux version of the app here.

Superheros with Bit

You might not need Redux

Not all React apps require Redux for state management. For example, I didn’t absolutely need to use Redux for the above shown app. You can write an application using nothing but React’s component state.

Converting the Redux App to React Context

First, let’s remove all traces of Redux from our code. Delete the Reducer.js and Actions.js from the src folder.

You can also remove the redux and react-redux dependencies. Simply run:

$ yarn remove redux react-redux

With that taken care of, let’s begin writing our app with React Context API.

To convert the previous app to Context API, I first need to create a context to store the data in. This context will replace the Redux Store.

I will also have to create a Context.Provider component, which will act just like a normal React component. This React component will have a state and props and a component lifecycle.

Create file named Provider.js in the src folder. Inside, store the default values in the state and pass them down in the render, through the value prop.

Note: You can use the same hero.js file as the one you had used in the Redux version of the app. Use the same code snippet as the one I had showed above.

In most cases, we’ll want to render {this.props.children}, as we’re likely to just render this component class high up in our component tree, above any place where we might want to consume it.

We want our action handlers to be able to modify our state, and propagate its value down. So we will create component methods that will call this.setState to trigger re-renders. Furthermore, if we want to make those action handlers available later down the tree, we will need to also make them accessible in the value prop.

To consume these values later in the component tree, we will need to create a ThemeContext.Consumer component. This component will need a render prop that will receive the above value prop as an argument.

Create another file called Consumer.js in the src folder and write the following code in it.

We will leave the App.js file as it is, except make sure to remove the import statement for react-redux and actions.js file. We only have one thing left to do. Open your index.js file and inside the render() function, wrap the App component inside <Consumer> component and wrap that inside the <Provider> component as shown here:

And with that, we have a simple React App that uses React Context API to manage its state.

Take a look at the entire code here:

Redux or Context — When To Use What?

Each app is different, which also means that each app’s needs are different.

Some apps may work best with Redux, while others may work best with React Context API.Others might even need something like Mobx or Mobx-State-Tree, and some may not need anything to handle their state at all.

So, how do you decide?

The answer comes with experience. But I will try help you make your decision by explaining some of the best use cases for Redux and React Context API.

Redux

The point at which we want to start thinking about Redux is when our app’s state is global.

But you will probably have very less amount of state that will be global.Which brings us to an important take away:

Use both local and global state in your apps.

Managing everything in Redux is overkill. It may have negative performance implications and will increase the complexity of your app, making it hard to refactor, and likely reduce the reusability of many of your components.

But I am going off on a tangent here. Getting back to the topic, when do I use Redux?

Say you are building a food-ordering website. We have a checkout page that shows the food items a user has ordered with their respective prices and the total checkout price.

We manage all of this state with a local component state in the Checkout component. Then, we implement the NavBar component which shows the number of food items in the user’s cart and the total checkout price.

But we already have this data managed and rendered in the Checkout component. Also, you want both the Checkoutcomponent and NavBar component to stay in sync as the contents of the cart change. In other words, we have two disparate components that need to read and possibly update the same state.

So you need to pull the checkout cart state of our Checkout component up into Redux. Once this data is in Redux, the Checkout and NavBar components can individually connect to Redux with the state and dispatch functions they need.

React Context API

React Context API is used when all you want is a simple state management. It can also be used in apps where you want to pass some props deeply without the overkill that comes with Redux or MobX.

But the new Context API has its own limitations.

For example, it encourages the use of immutable or persistent data structures or strict comparison of context values which might prove difficult because many common data sources rely on mutation.

Another limitation is that the new Context API only allows for a Consumer to read values from a single Provider type, one consumer to one provider. Unlike the current API, which allows a Consumer to connect to multiple Providers.

Conclusion

Redux has been around for a long time. It has stayed strong in the face of other state management libraries like Mobx and Mobx-State-Tree. As Redux 4 is coming out, sources say that it has neat improvements under the hood.

React Context API on the other hand, has given us a way to handle simple state related things, without having to get into bed with Redux. At the end of the day, every app is different so you can only try to make the best decision.

--

--