A Guide to React Context API and useContext hook

Share your love

React Context API was introduced a while ago with React version 16.3. It was mainly introduced to solve one of the major problems that React developers like us were facing at that time – prop drilling and component re-rendering. In this article, we will cover the reasons which led to the birth of Context API and a lot more topics related to it, with step-by-step explanations. 

Let’s Start…

What is React Context API

Context API in React JS is an in-built API feature of React that manages props and states globally. Simply said, it is a way of passing the data from the parent to the child component – its final destination, without going through every other child component. 

Imagine a situation where we have to pass down data from a parent component to the last child component. One way is to manually pass down the data in the form of props to every other child component until it has reached the last child component – where it has to be used. This process is called prop drilling and it complicates our overall code and can even make it ugly.

What we can do to avoid prop drilling is we can make a global state variable with the help of the createContext() function of React, store and fetch all state management variables from it using methods like Provider and Consumer and we can even use a hook to make things even simpler as they are globally stored. See the image below to understand.

React Context API
React Context API

What is Prop Drilling?

In a typical React Application, we use props to pass down the data from parent to child, but sometimes it can become a hassle as it can create a lot of complex codes mainly when we are dealing with some pretty heavy coding stuff like creating a UI Theme, Authenticating user credentials, etc. 

In cases like these, if we use the same method of passing props from parent to child component until it has reached its final destination, it will not become a thing of beauty but will become something so complicated that sometimes we, who have written it, might get confused, and this is called prop drilling.

Prop Drilling is a situation where data is passed down from a parent component to multiple child components until it has reached its final destination. Prop Drilling not only complicates our code but also consumes a lot of space and can sometimes be the root of re-rendering.

This is simply because whenever we pass a prop down the component, our App gets re-rendered and in the case of prop drilling, there can be multiple re-renders. To avoid this, the concept of Context API was introduced in both class and functional components. In this article, we will mainly see how to use React Context API with functional components. 

Prop Drilling
Prop Drilling

How does React Context API work?

Now that we have a basic idea of what Context API in React is all about, let’s understand its inner workings. As discussed it mainly requires the createContext() function which converts the local state and props into global state and props and stores them inside a separate container globally so that any child component can access it without actually passing down the line.

It is not that easy to create a context and use the data globally in any child component we wish to use it in. There are certain things we have to understand first, in order to properly make use of this amazing feature to make our app less complicated than it actually is.

Every time we use the createContext() function, will create an object with two properties – Provider and Consumer. The Provider property is used to make the data feasible by every other component in the component tree without actually passing it manually. This can be done by wrapping the data or function inside the Provider Component.

<MyContext.Provider value={/* some value */}>

Given above is the syntax of how we will wrap our data in the Provider Component. Here MyContext.Provider is the Provider Component in which we will be wrapping our data. The value prop is used to send the data down the entire component tree. Just write the data in between those braces and that data will be globally accessible.

And if we want to consume it in one of the child components, we just have to use the Consumer property as a Component just like we did in the case of Provider property. You can read more about React Context API in React Official Documentation.

<MyContext.Consumer>

  {value => /* render something based on the context value */}

</MyContext.Consumer>

One Provider Component can be connected with many Consumer Components.

Providers can be nested to override values deeper within the tree.

All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes.

Let’s understand React Context API with the help of an example

Understanding React Context API with the help of a code.

Now we know that for React context API we need mainly three things – the createContext() function, the provider component, and the consumer component which are again the properties of the createContrext() function. 

Let’s understand how to use them in a code with the help of a basic example. Say we have a parent component and three child components linked as shown below, and we somehow want to pass the states or properties of the parent component to the last child component. One way again is prop drilling which can be used here as the number of child components is less, but again the better approach will be to the context API. 

Parent and Child Component

Creating Context

In React, whenever we use any type of React’s feature we have to import it first from React and then we can use it just like we did whenever we use hooks in our app. So to create context our first step will be to import createContext from React and named it as First Name as shown below.

import { createContext } from “react”;

const FirstName = createContext();

This createContext function will return two properties Provider and Consumer which we have already discussed in the above sections.

Creating Provider Component

As told above Providers are the components that pass the globally stored value to the one who needs it or calls for it using the Consumer Component. 

import React from "react";
import { createContext } from "react";
import Child3 from "./Child3";


const FirstName = createContext();


const App = () => {
  return (
    <>
      <FirstName.Provider value = {"Ateev"}>
        <Child3 />
      </FirstName.Provider>
    </>
  );
};


export default App;
export { FirstName };

Here the App Component is the Parent component and as you can see, we are sending the data to the third child component without passing through every other child component. The value prop contains the value which needs to be passed as the prop

The provider component will only work if we wrap up the child component in which we have to use the data with the Provider Component as we did in the above code.

Do not forget to export this FirstName variable in which we have stored our global variable as we need to import it in our child component to extract this value through destructuring. 

Creating Consumer Component

Now in our Child3 Component, we have to accept the sent data which we can very easily do by the Consumer Component. But here is a catch, our Consumer Component only accepts a callback function. Normal statements will not work here. 

import React from "react";
import { FirstName } from "./App";
const Child3 = () => {
  return (
    <>
      <FirstName.Consumer>
        {(value) => {
          return <h1>My name is {value}a</h1>;
        }}
      </FirstName.Consumer>
    </>
  );
};


export default Child3;

As the Consumer Component needs a callback function, that means all the properties of a function are valid here giving the ability to destructure our object or value which we have given to our Parent Component or App.js.

According to React Documentation, Consumer Component accepts the callback function and returns a node which in our case is the h1 tag with a statement. We can add more statements to it depending on the need of the app.

The value argument used in this callback function will be equal to the value prop passed from the Provider Component, or if by chance there is more than one Provider function, this value argument will have the value of the value prop passed by the closest Provider Component.  And if there is no Provider for this context above, the value argument will be equal to the defaultValue that was passed to createContext().

React Context can also be messy sometimes…

This whole blog is about finding a solution for prop drilling so that we can make our code less complex and messy and for that, we are using Context API. But what if the same context API creates a mess? Let’s say we have to pass another value irrespective of the previous value or object. We again have to follow the same steps as in creating a Context with the help of the createContext() function followed by the Provider Component and Consumer Component.

Let’s understand this concept with the help of the same example above but this time by adding a new value – LastName as well.

import React from "react";
import { createContext } from "react";
import Child3 from "./Child3";


const FirstName = createContext();
const LastName = createContext();
const App = () => {
  return (
    <>
      <FirstName.Provider value={"Ateev"}>
        <LastName.Provider value={"Duggal"}>
          <Child3 />
        </LastName.Provider>
      </FirstName.Provider>
    </>
  );
};
export default App;
export { FirstName, LastName };

In the above code, we can clearly see that we have to create a new Provider Component for every CreateContext() function. Until now there is no problem, but the complexity starts from the child component as there can only be one callback function, and for every Provider, we have to create a Consumer as then only we will be able to use that passed value. 

import React from "react";
import { FirstName, LastName } from "./App";
const Child3 = () => {
  return (
    <>
      <FirstName.Consumer>
        {/* there can be only one callback fumction for one Consumer */}
        {(fname) => {
          return (
            <LastName.Consumer>
              {(lname) => {
                return <h1>My name is {fname} {lname}</h1>;
              }}
            </LastName.Consumer>
          );
        }}
      </FirstName.Consumer>
    </>
  );
};


export default Child3;

It should be obvious from the above code the level of complexity we will be facing if this goes on and somehow we have to deal with four or five consumers. It is as complex as it is messy and can be termed the callback hell in programming terms. 

Well, React developers have found a way to use all the functionalities of React Context API while not falling into this callback hell trap. That way is to use the useContext hook which was introduced in React version 16.8 with other hooks.

What is useContext Hook?

useContext Hook in React is just another hook that was introduced to give the functional component the same power that the class component used to have at that time, but it somehow overpowered them. It follows all rules that another hook follows like its importing it before using it, and cannot be used inside a function, etc.

As the name suggests, the useContext hook lets us use the Context without a Consumer. It’s better to understand this with the help of code rather than text.

import React from "react";
import { useContext } from "react";
import { FirstName, LastName } from "./App";
const Child3 = () => {
  const fname = useContext(FirstName);
  const lname = useContext(LastName);
  return (
    <>
      <h1>My name is {fname} {lname}</h1>
    </>
  );
};


export default Child3;

Now compare the two scenarios, one in which we were using Consumer Component to get the data that we were passing using the Provider Component. In the case of the Consumer Component, we were using render props that were causing a callback hell.

But when we are using the useContext hook in place of Consumer Component sharing and receiving of data becomes much simpler than it usually was, to begin with, and our code becomes a lot less messy and complex.

The working of the useContext hok is exactly the same as Consumer Component. The only difference is in the Consumer component, we have to create a callback function to capture the value of the value prop of the Provider component and use it in our React application. 

But in the case of the useContext hook, we no longer have to create that function, we simply have to pass the context object in the useContext hook as shown which will return a value that will be equal to the value we have sent to the context using the value prop of the nearest Provider Component and save it in a new variable which will be used in our app dynamically.

When the nearest Provider Component updates, this Hook will trigger a re-render with the latest context value passed to that Provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a re-render will still happen to start at the component itself using useContext.

A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

React Context API VS Redux

React Context APIRedux
It is a built-in state management toolAdditional installation is required
Short and Easy learning curve with less codingcomplexes overall code by creating unnecessary work
No extra libraries are needed as it is a built-in tool, thus the overall bundle size gets reducedAs it is an external package, the size of the bundle gets increased.
Re-rendering happens on every state change of all state components.Re-rendering happens on every state change but only on updated components
Requires minimal setup and is perfect for small applications.Requires extensive setup and is perfect for large applications.
Debugging can be hard due to React’s nested structureDebugging is easy due to Redux Dev Tools
It works by creating a Provider and Consumer component, allowing you to pass the state down the component tree without having to manually pass props.It uses reducers, actions, and middleware to manage the state in a more organized manner
Time traveling is not possibleTime traveling is possible

Summary

In this blog we have mainly discussed what is context API and how can it make our life and code simpler to understand rather than using the same old method of prop-drilling. React is all about sharing data between two components mainly between a Parent and a Child Component using props. But when we have to use some data that has been initialized in the parent component in the last child component, we only have one way of passing that data to it and it is through prop drilling.

But this all changed when React Context API was introduced with React version 16.3. In this, we make that data (value or object) globally available by storing it somewhere else using the createContext function and then passing it from the parent component using the provider property of the creaetContext() function and will be used by the consumer property of the createContext() function both these properties are used in the form of a component.

But there are times when we can’t use the Consumer Component as it will create a callback hell as described in the blog. We can easily avoid it by using a hook called useContext hook which was again introduced with React version 16.8 and can only be used in functional components. 

In this, we just have to create the context and pass the value using the Provider function but the one receiving those values will not be the Consumer Component but the useContext hook. In this hook we don’t have to do anything; rather all the work will be done by this hook, we just have to initialize the same number of variables as the data we have passed and give them the original passed value, using the useEffect hook which then will be used in our app dynamically.

Frequently Asked Questions

What does the useContext hook do?

React Context APi

The useContext hook is used to access data that has been passed down from a higher-level component. It allows you to access data that is stored in the context object without having to manually pass it down as props.

What is the difference useContext hook and useState hook?

  1. useContext allows you to access data that is stored in a global context and pass it down through components, while useState allows you to store and manage state within a single component.
  2. UseContext is most useful when you need to access global data, while useState is best used within the component.
  3. useContext allows you to keep your global state in one place and have it update automatically whenever that state changes, while useState requires that you manually update the state within the component whenever the data changes.

What is the difference between useContext and Redux?

UseContext is a React hook that allows you to access and update content from anywhere in a component tree. It is most useful for sharing data that is considered “global” for a tree of React components. Redux is an open-source external library that provides a state container for JavaScript applications. It acts as a centralized store for all the components in an application, with rules ensuring that the state can only be changed in a predictable fashion. Redux is more powerful and versatile than useContext, but is also more complex to set up and use.

Difference between Context API and Redux

The primary difference between the Context API and Redux is the way data is managed. The Context API is a built-in method of managing data within React, while Redux is an external library used to manage data within React. The Context API allows components to access data without having to pass props through multiple components, while Redux stores data in a single global state, which can be accessed by any component. Both are powerful tools for managing data in React, but they serve different purposes and should be used accordingly.

When to use Context API in React?

Context API can be used to avoid prop drilling in our app or simply to avoid manually passing the props down the entire tree by making that specific data globally accessible to all child components

When not to use Context API in React?

Context should not be used when

  • There are fewer props to pass
  • The logic is too complex
  • The receiver component is outside of the Component tree
Share your love