React – Context API

A typical React application can have a deeply nested tree of components. If the state is stored high up in the Component tree, it isn’t easy to pass the data to one of the deeply nested components.

In React, the data flows down in one direction from parent to child components through props.

Suppose any descendant component needs to access data from an ancestor component. In that case, we will have to repeatedly pass props until the deeply nested child receives the data stored high up in the component tree.

Code Example for the above tree in the image:

function Root() {
    /* pass ‘status’ state to C2 from the root component */
    const [status, setStatus] = useState('inactive');
    return ( < C1 / >
        <C2 status = {
            status
        }/>
    )
}

/* pass the received ‘status’ prop to C5 from C2 component */
const C2 = props =>
    <>
    <C4 />
    <C5 status = {
        props.status
    }/>  
        </>

/* pass the 'status' prop to C6 from C5 component */
const C5 = prop => < C6 status = {
    props.status
}/>

/* the deeply nested component C6 finally receives 
the status property stored in the Root component */
const C6 = props => < div > {
    props.status
} </div>

This way of passing down data is called prop drilling. A descendant component cannot directly access the data of an ancestor. It has to be passed down in a step by step manner through props.

In the example, C2 and C5 do not need the ‘status’ prop but have to still pass it down to C6 from the Root component, just because C6 needs for its purpose.

This way of deep prop drilling is often confusing and makes code hard to change and debug. React’s context API solves this problem.

Concept of The Context API

React Context API provides a direct way to communicate and share data between an ancestor and descendant component. No prop drilling is required to pass the data down in the tree.

The Context API stores the data in a separate/external place with a broader scope from the component’s normal state and props.

State and props are internal to a component and have a local scope, so it has to be passed to every child that needs it. But, a context is external and independent from the component.

We can create a context for any component and make it available to all of its descendants. Once the parent stores the data to the context object and provides it, any of its child components can consume that Context.

This is how a component can share data and communicate with any deeply nested child component by using the Context API.

Providers and Consumers

Once we create a context using createContext(), we get back a context object. The context object has a Provider and a Consumer component.

It is actually through Provider and Consumer that we implement the context API. It allows us to share and receive data from the parent and child components, respectively.

Context Provider

A Context Provider is a component that makes the context value available to all of its descendants. It is used on the ancestor/parent component.

Context Consumer

A Context Consumer is also a component, but it is used to receive the value shared by its closest ancestor Provider. It is used on the descendant/child component.

Three steps to use the Context API

1- Create the Context (Create the data store)

First, we must create a context and store it somewhere.

const MyContext = React.createContext();

2- Provide the data (Make the data available)

The Context object’s Provider component will broadcast the data value to all its descendant components.

<MyContext.Provider value={{ status: ‘inactive’ }}>
      {/* render the child components */}
</MyContext.Provider>

3- Consume the data (Use the data)

Now, the context data is available to all Provider descendants no matter how deeply it is nested. However, the component that wants to use the data must use a context Consumer to retrieve the broadcasted value.

<MyContext.Consumer>
{value => (
    <div>{value.status}</div>
)}
</MyContext.Consumer>

Creating A Context

Before doing anything related to the Context API, we need first to learn to create a context. React provides a method called createContext() that creates a new empty context.

A context is just a space in memory that a component can use to store some data and share its descendant components.

We can define the new Context in a separate file and export it to import all the components that need the Context.

MyContext.js
      import React from 'react'
      /* store the returned context object inside a variable */
      const MyContext = React.createContext({ status: 'inactive' });
      export default MyContext;

You can optionally pass a default value as React.createContext(defaultValue)

This default value will be used by the Consumer Component if no Provider component is found upwards the component tree.

Providing The Context Data

Once we have defined a context object, we need to use its Provider Component to share data with the descendants.

The Provider component takes a value prop. You can pass it any value (object, array, string, number) you want to share with the descendants. It stores the passed value inside the Context, which can be consumed later.

App.js
import MyContext from './MyContext.js';
import MyChild from './MyChild.js';
import { useState } from 'react';
function App() {
const [status, setStatus] = useState('active');
return (
        {/* the status state is passed as the context value */}
        <MyContext.Provider value={{ status: status }}>
       		 <MyChild />
        </MyContext.Provider>
        )
}
export default App

You can pass any data (state, props, variables) as the context Provider’s value.

Consuming The Context Data

The shared context data does not become automatically accessible inside the Descendant Component. Data being available does not mean it is accessible.

The data must be consumed using the < Context.Consumer /> component. After that, it will be accessible inside the descendant component.

<Context.Consumer /> expects a function as a child. It has access to the context value from the Provider. The function should return the JSX code to render. You can use the context value inside this child function.

MyChlid.js
import MyContext from './MyContext.js';
const MyChild = () =>
    <MyContext.Consumer>
        {value =>
          <div>
          	<h3>The status is: {value.status}</h3>
          </div>
        }
    </MyContext.Consumer>
export default MyChild;

Any changes to the context value in the Provider reflect automatically inside the Context Consumer. It works just like using props where react updates the component on any prop changes.

So there is no problem in passing state as a context value.

Context Type Compatibility

A context object has a different type from another context object. The context types must match to share data between a Provider and a Consumer.

Therefore, to access a particular Context Provider‘s values, you can use the same context object for the Consumer too. This is why we extract the context definition into a separate file to import any component that requires that context object.

A simpler way to consume data

[The useContext() hook]

<Context.Consumer> works in both class and functional components. But code looks ugly to use <Context.Consumer> and then provide a child function that has to return some JSX.

For functional components, there is a hook called useContext() that provides a simpler way to access the context data.

useContext() takes the context object as a parameter and returns the value from the nearest context Provider of the same type. Here is the same component is written using the useContext() hook:

import MyContext from './MyContext.js'; 
import { useState, useContext } from 'react' 
function MyChild(){
        const value = useContext(MyContext);
        return (
          <div>
               <h3>The status is: {value.status}</h3>
          </div>
    )
}
export default MyChild

Summary

We have discussed the difficulty of sharing data using props if the child component is deeply nested in the tree (common for medium and large scale apps). It leads to the problem of prop drilling.

The ContextAPI provides a cleaner way to share data between an ancestor and any descendant component without passing props. It uses the Provider concepts that share the data (i.e., the ancestor) and a Consumer (i.e., the descendant component) to pass and receive data, respectively.

Many popular state management libraries like Flux and Redux use the ContextAPI as their core.

Back to: React Tutorial > Learn React

Leave a Reply

Your email address will not be published. Required fields are marked *