The following is based on a tach talk I did for JS World Conference Amsterdam. You can find the test code here: https://github.com/DanailH/jsworldconference-2021
React Context is one of the most powerful features of React. Using React Context API, we can create global variables that can be passed around anywhere in the application. It is basically an alternative to prop drilling or even to state management libraries such as redux to some extent.
Before moving to React Context API, let’s discuss prop drilling and state management in React.
Passing props from the grandparent component to the parent component and further to the child component and so on, is known as prop drilling. It is not a big deal to pass data from a parent component to a child component but prop drilling can become a problem.
Props are not the only part of composable React components. Another important aspect of building composable React components is the React state.
The state of a component controls its behavior. In React, we can have a component-level state as well as a global state. While React provides in-built support for component-level state, it is recommended to use third-party libraries for managing the global state.
Redux is the most popular state management library. Though it is absolutely fine to use Redux, its implementation is quite complicated. Redux should be used when the application is huge. But when the application is not that huge, we may feel implementing Redux is time-consuming and requires more effort.
Understanding the need for React Context API
Why do we actually need to care about the React context API when everything is fine with prop drilling and redux? Suppose we have five components in our React application — App, Component 1, Component 2, Component 3, and Component 4.
The structure is as follows:
Suppose we need to pass the value of count from the App component to Component4. We can simply pass count as prop from App to Component1, then from Component1 to Component2, then from Component2 to Component3, and finally from Component3 to Component4. Everything will work fine, but there is no need of count in Component1, Component2, and Component3, right? These components are receiving and passing down the props unnecessarily.
With the React Context API, we can avoid this unnecessary involvement of the middle components. The context will only involve the App component and the component that requires the prop, i.e. Component4.
Another situation where we may need Context API is for global state management. As mentioned earlier in this article, heavy implementation of Redux can be avoided if the application is not that big. Moreover, the introduction of the useContext() hook has made the job easier.
Using context to avoid prop drilling
We will discuss how to use the Context API to avoid prop drilling with the help of a simple example. This basic React application will consist of four components — App, Component1, Component2, and Button. The structure will be as follows:
The App component will pass the value for the background color for the button through props. Let’s implement the code.
Following is the App.js file:
In the above code, the App component renders Component1. The value of color is passed as props.
Now, observe the Component1.js
Component1 renders Component2. Again the value of color is passed to Component2 as props.
Next, observe the Component2.js.
Button component is being rendered by Component2. Similarly, the value of color coming from Component1 is passed to the Button component.
Finally, we can use the value of color in the Button component.
This entire code will work as we want but we are involving Component1 and Component2 unnecessarily. Let's see how we can use the React Context API to simplify the solution.
Let’s create the equivalent of the above application using context.
First, we need to create context. So let’s create a file solely for the context.
To create a context, we need to import React from ‘react’ and use it to invoke the createContext() method. Now, we can use it in other files.
Remember, we only need to involve two files — one that is sending data (App.js) and the other that is receiving data (Button.js).
So let’s start with App.js.
App.js is the sender. In other words, App.js is the provider. So using the Context we created, we use the Provider and pass the value through it.
Now, let’s use this value in the Button component.
First, we need to import the context here also because just like the Provider, we need the Consumer to use the context.
Now, we can use the value that we passed through the Provider.
That’s it! The value passed from the App component is accessible in the Button component. No other component was involved.
Using context for state management
As discussed earlier, we can use the React Context API for moderate state management. Let’s understand with the help of an example.
To understand this example, you should have knowledge of React hooks. We will use two React hooks — useContext and useReducer.
According to official React documentation, the useContext hook is equivalent to the <MyContext.Consumer>. We will use this hook because it’s much easier to work with it. Another hook is useReducer which will help us implement reducer-like functionality without Redux.
We will create a simple React application with three components. Two of these components will have a button. These two buttons will manipulate the state and the state will be displayed in the third component. So we will have a global state which will be shared by all the components.
The working of the application is as follows:
- The initial value of state is 0 and it is displayed in the Display component.
- The first button will increase the value by one.
- The second button will decrease the value by one.
Let’s start by creating the context for the application.
We need a reducer. Let’s create one.
The reducer can increment the value by one or decrement it by one.
We have the context and reducer. Now, we can use both of them in the App.js file.
Observe the following lines.
First, we created an initial state. Using the useReducer hook, we create a state and a dispatch function. Remember, we have to pass the reducer and initial state to this hook.
Next, we will use the state and the dispatch function with the Provider.
Now we can use the state and dispatch anywhere in the application. Let’s start by dispatching the actions on button click.
Observe the Button1.js file.
To use the dispatch function, we need the useContext hook.
Now we can use the dispatch function.
Similarly, we can use the dispatch function in the Button2.js file.
Now, the state will be updated accordingly.
Next, we have to use the state in the Display component.
Again we have to use the useContext hook but this time for accessing the state.
Every time the state is updated by any button, it will be updated in the Display component as well.
Wrapping it up
Props and state are two important aspects of React. Both of them should be used efficiently. If we don’t handle them properly, we may end in unwanted situations like prop drilling. React Context API is an advanced and very powerful feature that can be used to avoid such unwanted situations.
In this article, we discussed what React Context API is and how it is used to avoid prop drilling. We further discussed how the Context API can be used as an alternative to Redux in moderate state management.
Using Context API is not tough. We have to create a context using React’s createContext method. Then we have to use the context’s Provider and Consumer to pass and use the value respectively. With the useContext and useReducer hooks, we can work with context more easily and simply.