Mastering React Context: Simplifying State Management and Prop Drilling
Tags: react, javascript, webdev, beginners
Published: 2024-03-10 05:29:15

Many times, in react, state information is used by multiple components. Information in React is usually shared using props. We use it for this purpose as well. 

To resolve such situations, we define that data/function at a common parent component and then move it down using props. We call it **lifting the state**.

![Independent states vs lift up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oh8lslonzfw5pktya98h.jpg)

The **Context API** is an alternative to this. A context defines a scope with some information. All the components placed in this scope can use that information directly. In my opinion, it is similar to namespaces in C++.

## The Problem with Props

Although passing props is a perfectly valid solution, it often creates an issue. There might be situations where the common parent is far from the actual component. So, we will need to move the data very deep down the UI tree and make it unnecessarily verbose. This issue is called **"Prop Drilling"**.

![Prop Drilling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47d3k29bkbeea5q46ue0.jpg)

Let's take an example to understand this. Let's say we have a CRUD app related to Books. Following is the structure of this application.

![Books app using prop drilling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4qeth0hhh6hrzeeev6l.jpg)

`<BookCreate />` - Creates entry for the book.
`<BookList />` - Shows all the Books
`<BookShow />` - Display component for each book.
`<BookEdit />` - Component to editing book information.

The **`books`** array is a state variable that contains all the books, and **`createBook`**, **`editBook`**, and **`deleteBook`** are functions that manipulate the state.

As we can see, all the components use the **`books`** state. So, we declare in the App component. So are the functions that manipulate it.

Now, these functions need to be moved way down the component tree. We will require a lot of props, resulting in prop drilling.

We can avoid this issue using Contexts. Context lets a parent component provide data to the entire tree below it.

## Implementing Context

To implement a Context, you need to do the following three steps:

1. Creating a context
2. Provide the context scope
3. Use the Context

### Creating a Context object
  
We use the **`createContext()`** method for this.
```jsx
import {createContext} from "react";
const Context = createContext(defaultvalue);
```
This method returns a **`context`** object. 

### Providing the Context 

Now, we create a scope using this **`context`** object. Here, we define the values to pass to the child components. The **`context`** object has a provider component for this purpose.

```jsx
<Context.Provider value={valueToShare}>
      {children}
</Context.Provider>
``` 

This component has a **`value`** prop through which we associate values with the scope. The value of this prop becomes available to all the children components of the **`Provider`** component.

### Using the `context` object

The children components extract these values using the **`useContext()`** hook.

```jsx
import {useContext} from "react";
const value = useContext(Context);
``` 

This hook takes the **`context`** object as an argument and returns the values that are associated with it. 

Here's an example with a simple implementation using contexts.
```jsx 
//Context.jsx

import { createContext } from "react";

const Context = createContext(1);

export default Context;
```

```jsx 
//Container.jsx

import React, { useContext } from "react";
import Context from "./Context";

function Container() {
  const value = useContext(Context);
  return <div>{value}</div>;
}

export default Container;
```

```jsx 
//App.jsx

import React from "react";
import Context from "./Context";
import Container from "./Container";
function App() {
  return (
    <Context.Provider value={5}>
      <Container />
    </Context.Provider>
  );
}

export default App;
```
[![Edit context-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/context-example-fu8ndo?fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FApp.jsx,%2Fsrc%2Fcomponents%2FContainer.jsx,%2Fsrc%2Fcomponents%2FContext.jsx&theme=dark&moduleview=1&view=split)

This way, information is shared between components without using any prop for passing data.

## Some Extra bits on contexts
  
- We can create multiple scopes using the same context object. We can provide each of them with different context values that the components within them can access. Replace the App code with the following code in the above example to see it live.

   ```jsx
   function App() {
     return (
       <div>
         <Context.Provider value={3}>
           <Container />
         </Context.Provider>
         <Context.Provider value={7}>
           <Container />
         </Context.Provider>
       </div>
     );
   }
   ```
  
-  We can also define a scope within another scope of the same context object, and both will provide different values to their child components. Here, a child can access all the data values belonging to the nearest parent Provider. Replace the App code with the following code in the above example to see it live.

```jsx
function App() {
  return (
    <div>
      <Context.Provider value={3}>
        <Container />
        <Context.Provider value={7}>
          <Container />
        </Context.Provider>
      </Context.Provider>
    </div>
  );
}
```

- If a non-child component tries to use it, it gets the default value passed during the **`createContext(defaultValue)`** call. In such a situation, React searches for a parent provider for it. But as it does not find one, it returns the default value as a fallback. Replace the App code with the following code in the above example to see it live.

```jsx
function App() {
  return (
    <div>
      <Context.Provider value={3}>
        <Container />
      </Context.Provider>
      <Container />
    </div>
  );
}
```

## Practical Use Case

The most common use of contexts is **State Management**. Prop drilling often happens due to the sharing of state information between components. What would be better is to create a context for the state and put the component tree associated with that state within that.

Let's see our book example again. We declared the **`books`** state and associated methods in the **`App`** component. We then moved them to the actual location via props.

Alternatively, we can define the **`books`** state and the methods in a context and provide it to the App component. This way, the App and all its children components have access to the **`books`** state directly.

![Books app using context](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hyg0ilmf2tt6vbobsljd.jpg)

Here's the code for it

{% codesandbox bs3rzk %}

## Some Consideration

Contexts are great tools for passing data deeply in a UI tree without tending to prop drilling. They are simple to implement, making it very tempting to use them. Thus, we may end up overusing them. To avoid this, we must understand the association between the information and the components using it.

- If the information is associated with components which are related and mutually dependent on one another, it might be better to use the prop system as it would make the data flow explicit between them. For example, let's say we have a Form component with multiple form controls. Different form controls may depend upon the states of one another and thus require the sharing of information. Here, using props would be better as it would depict how data flows within the Form component.

- On the flip side, if the information is associated with mutually independent components, it is better to use contexts. An example of this could be login information. We can use this in the navbar to show the username. We can use this on the profile page to display user details. We can use it application-wide, irrespective of the relationship between the components.

## That's all folks

Contexts are great for sharing information when it needs to be moved deeply in a component tree. It is simple to implement and helps in clubbing data in one place.

This article is my understanding of the context API. You may give your insights in the comments. I would appreciate your feedback.

And hey, if you want to connect beyond these pages, catch me on Twitter! My handle is [@AnshumanMahato_](https://twitter.com/AnshumanMahato_). 

With this, I would like to conclude this post. Until next time, stay curious and keep exploring! 🌟Thank You for reading this far. 😊