Using React Context to communicate between Components

July 4, 2020
by Moiz Shaikh
  • React Context Featured

In the previous article we saw how components communicate in react. Passing information from Parent to Child component and Vice versa. We also saw two way data binding in react. In this article we will introduce React Context, an another feature which lets you share data across different components.

How is it different than the traditional parent and child communication? Well, using React Context allows you to avoid prop drilling which you would typically do when passing data from one component to another.

React Context

In a typical component tree, you may have multiple components in a hierarchical structure. You may often come across situations, where you would want to pass data from the highest level component to the lowest component in the component tree.

One way to do it, is to pass the data through all the intermediate component. But the information required by the receiving component is quite irrelevant to the intermediate components. This is where React context comes handy. React Context makes it possible for components to consume data, without the need of passing data through intermediate components. It essentially broadcast’s data which can be accessed by any component below.

Prop Drilling Data

Let us consider an example of an Author’s page. We have an <Author/> component where we fetch an authors information. We pass the data to the <Layout/> component and the layout component then passes the data to be rendered to the <Description/> and <Avatar/> component respectively.

App.js
import React, { Component } from 'react';
import Layout from './Layout';

class Author extends Component{

  state = {
    about: "This is an about text for author",
    avatar: "https://ciphertrick.com/wp-content/uploads/2020/06/Moiz.jpg",
  }

  componentDidMount(){
    // Fetch Author data and store it in the state
  }
 
  render(){
 
   return  <React.Fragment>
              <Layout
                about = {this.state.about}
                avatar = {this.state.avatar}
              />
           </React.Fragment>
           
  }

}

export default Author;
Layout.js
import React from 'react';
import Avatar from './AuthorAvatar/Avatar';
import Description from './Description/Description';

const Layout = (props) => {
    return <div style = {{display: 'inlineBlock'}}>
              <Avatar avatar = {props.avatar}/>
              <Description description = {props.description}/>
           </div>
  }


  export default Layout;
Description.js
import React from 'react';

const Description = (props) => {
    return <div style = {{display: 'inlineBlock'}}>
             {props.about}
           </div>
}

export default Description;
Avatar.js
import React from 'react';

const Avatar = (props) => {
    return <div style = {{display: 'inlineBlock', width: '100px', height: '100px'}}>
                <img src = {props.avatar} alt = "author-avatar"
                     style = {{width: '100px', height: '100px'}}
                />  
            </div>
}
 
export default Avatar;

In the above code, we are essentially passing data down the chain. But if you look closely, the <Layout/> component doesn’t use any of the data, but rather passes it down to the <Avatar/> and <Description/> component respectively. In bigger applications you might have several such components who passes data down the line. This is where we can use React Context.

Creating A Context

let us start by creating a context.

context.js
import {createContext} from 'react';

const Mycontext = createContext();

export default Mycontext;
Inside the createContext method you can pass an optional parameter to initialize the context with a default value. In our case, we won’t be passing any data yet.

Providing A Context

Now inside our app component we will import the context that we just created. We will then wrap our Layout component with Mycontext.Provider component. The Provider component contains a value property that you can use to pass data. Here, we’ve passed an object containing the Author’s avatar and about.

Layout.js
import React, { Component } from 'react';
import Layout from './Layout';
import Mycontext from './context';


class Author extends Component{

  state = {
    about: "This is an about text for author",
    avatar: "https://ciphertrick.com/wp-content/uploads/2020/06/Moiz.jpg",
  }

  componentDidMount(){
    // Fetch Author data and store it in the state
  }
 
  render(){
 

    return <React.Fragment>
                <Mycontext.Provider value = {{avatar:this.state.avatar, about :this.state.about}}>
                  <Layout/>
                </Mycontext.Provider>
           </React.Fragment>
  }

}

export default Author;

If you compare the code wrapped in React.Fragment with the previous one, you will notice that we no longer explicitly pass the data through the Layout component. Instead we pass the same data as a value to Mycontext.Provider wrapper component. What this does, is it now makes the value prop available to the entire Layout component Tree. Any component that is a child, a grandchild and so on of the Layout component will now be able to use that data. To do so, they simply have to consume it.

Layout.js
import React from 'react';
import Avatar from './AuthorAvatar/Avatar';
import Description from './Description/Description';

const Layout = (props) => {
    return <div style = {{display: 'inlineBlock'}}>
              <Avatar/>
              <Description/>
           </div>
  }

export default Layout;
In the Layout component we no longer forward data explicitly to the Avatar and Description component, but simply invoke them.

Consuming A Context

Let us now consume the data that was passed through the context provider from the App component in the Avatar and Description components. Data can now be accessed inside the Consumer wrapper component. It accepts a function where the data sent from the provider is received as a function parameter. We are essentially destructuring the about property that we sent from the app component. Similarly, for the Avatar component.

Description.js
import React from 'react';
import Mycontext from '../context';

const Description = (props) => {

 return <Mycontext.Consumer>
          {
              ({about}) => (
                  <div style = {{display: 'inlineBlock'}}>
                      {about}
                  </div>)
          }
        </Mycontext.Consumer>
}

export default Description;
Avatar.js
import React from 'react';
import Mycontext from '../context';

const Avatar = (props) => {

    return <Mycontext.Consumer>
            {
                ({avatar}) =>
                    (<div style = {{display: 'inlineBlock', width: '100px', height: '100px'}}>
                        <img src = {avatar} alt = "author-avatar"
                             style = {{width: '100px', height: '100px'}}
                             />  
                    </div>)
            }
           </Mycontext.Consumer>
  }
 
export default Avatar;

Accessing Context outside of Provider and Consumer

Till now, we’ve been only accessing context Data inside the Mycontext.Provider and Mycontext.Consumer Wrapper components. What if you need to access Context in certain Lifecycle methods like componentDidMount and componentDidUpdate or what if you want to access it outside of Consumer in a functional component? Well you can do both.

We have our Layout component which is as Class based component. To access the Context outside of the Provider, you will have to assign contextType property to your context.

To do so, we will add the following line of code.
static contextType = Mycontext;

App.js
import React, { Component } from 'react';
import Layout from './Layout';
import Mycontext from './context';


class Author extends Component{

  state = {
    about: "This is an about text for author",
    avatar: "https://ciphertrick.com/wp-content/uploads/2020/06/Moiz.jpg",
  }

  static contextType = Mycontext;

  componentDidMount(){
    // Fetch Author data and store it in the state
  }
 
  render(){
   // You can use this.context to access context here or any lifecycle method
   return  <Mycontext.Provider value = {{avatar:this.state.avatar, about :this.state.about}}>
              <Layout/>
           </Mycontext.Provider>
           
  }

}

export default Author;

You will now be able to access your context using this.context anywhere outside of your Provider.

As for Functional components, We will have to use React Hooks useContext to achieve this. The useContext will accept the context that we created, i.e Mycontext and will return the current value of that context. Let us modify our Functional Avatar and Description components.

Avatar.js
import React, {useContext} from 'react';
import Mycontext from '../context';

const Avatar = (props) => {

    const Context = useContext(Mycontext)
             
    return  <div style = {{display: 'inlineBlock', width: '100px', height: '100px'}}>
                <img src = {Context.avatar} alt = "author-avatar"
                     style = {{width: '100px', height: '100px'}}
                />  
            </div>
           
  }
 
export default Avatar;
Description.js
import React, { useContext } from 'react';
import Mycontext from '../context';

const Description = (props) => {

  const Context = useContext(Mycontext)

  return <div style = {{display: 'inlineBlock'}}>
              {Context.about}
         </div>
           
}

export default Description;

In the above 2 snippets, we no longer use the Mycontext.Consumer but rather make use of useContext and access the about and avatar property. This was all about React Context and the way to use it. If you have any queries feel free to drop a comment below.

 

About

Jr. Software developer, Skilled in frontend technologies like React and Angular | Backend technologies like node and Express | SQL and NoSql Database.

Get notified on our new articles

Subscribe to get the latest on Node.js, Angular, Blockchain and more. We never spam!
First Name:
Email:

Leave a Comment