DEV Community

Mariusz for Pagepro

Posted on • Edited on

What is React Context And How To Use It?

If you're having a problem with passing property to a component just to pass it further down to child, React Context is exactly what you need.

By the definition, React Context provides us a possibility to pass data through the component tree, so you don’t need to pass props down manually at every level.

In other words, we can compare context to a global object of our React App.

Prop drilling problem

Tree structure components without context

React components structure is like a tree. Each child has only one parent and everyone is connected to the main root component. Thanks to this structure we have only one direction flow — we can pass props from top to bottom.

When we need to pass prop through a lot of components (f.ex from root to A3) it becomes a little bit annoying, and we called it a prop drilling problem. React Context comes to the rescue.

Tree structure components with context

When we need to make some of the data global in our app, or we would like to use them in a few components on a different deeply nested levels in the app structure, we definitely should use React Context.

It gives us access to the data on each level of our React App tree structure.

How to create Context?

The way of creating context is to import createContext method from React library and invoke it with defaultValue - it is not required but can be helpful when a component has not matched Provider in the tree.

Moreover, using defaultValue during creating React Context is important in testing component as separated from others.

import { createContext } from 'react'
createContext('defaultValue')

Example of creating Context

export const CountryContext = createContext({})
export const LanguageContext = createContext('en')

TIP: Good practise is to have separate file for creating contexts.

How can we pass down Context?

Create Context method returns an object with Provider and Consumer.

Thanks to Provider we can pass props down in our app structure. Provider component has a prop - value - which allows us to pass data assigned to this prop to all descendants (in value we can pass an object, number, function etc...). One Provider can be connected to many consumers.

Furthermore, Provider can be nested, thanks to that we can override passed data in value prop deeper within the app.

If value prop changes all consumers of a Provider will re-rendered.

const { Provider } = createContext('defaultValue')

Example of using Provider

<CountryContext.Provider
  value={{
     setSelectedCountry,
     selectedCountry
   }}
>
  <LanguageContext.Provider
    value={{
       lang: selectedLanguage,
       setSelectedLanguage
     }}
  >
    <header> ...
    <main> ...
    <footer>... 
  <LanguageContext.Provider>
</CountryContext.Provider>

How can we get Context?

We can have access to data which we passed to value prop in Provider thanks to subscriber called Consumer.

The Consumer component requires a function as a child that has the context current value in an argument and returns a React Node element.

const { Consumer } = createContext('defaultValue')

Example of using context by Consumer

<CountryContext.Consumer>
  {({ selectedCountry }) => (
    <h1>
      {selectedCountry.name}
    </h1>
  )}
</CountryContext.Consumer>

In this example we use CountryContext to have access to selected country. We create function returning country name which we received in an argument of it (the newest applied context).

Example of using Context Consumer as a hook

import React, { useState, useContext } from 'react'
import axios from 'axios'
import { CountryContext } from './contexts'
import { pushErrorNotification } from './utils'

const SearchBox = () => {
  const [searchValue, setSearchValue] = useState('')
  const {
    setSelectedCountry
  } = useContext(CountryContext)

  const searchCountry = () => {
    axios.get(`${endpoint}${searchValue}`)
      .then(({ data }) => {
        setSelectedCountry(data)
      })
      .catch(() => pushErrorNotification('Sth went wrong.'))
  }

  return (
    <div className="search-wrapper">
      <input
        type="text"
        id="search"
        name="search"
        value={searchValue}
        placeholder="Search for..."
        onChange={({ target }) => setSearchValue(target.value)}
      />
      <button onClick={() => searchCountry()}>
        Search
      </button>
    </div>  
  )
}

export default SearchBox

Here we have a SearchBox component where we can type desirable country name and find some info about it. Thanks to useContext hook, we can quickly set found country on current displaying details by setSelectedCountry method.

Easy access to Context

In the documentation, we can read that:

The contextType property on a class can be assigned a Context object created by React.createContext().

This lets you consume the nearest current value of that Context type using this.context. You can reference this in any of the lifecycle methods including the render function.

ComponentA.contextType = ContextB
OR
static contextType = ContextB

Example of using context by ‘this’

static contextType = CountryContext

render () {
  const {
    selectedCountry,
    selectedCountry: {
      borders = []
    }
   } = this.context
}
import React from 'react'
import { CountryContext } from './contexts'

class CountryDetails extends React.Component {  
  render () {
    const {
       selectedCountry: {
         capital,
         region,
         subregion,
         area,
         population,
         timezones = []
       }
     } = this.context

     return (
       <div> ...
     )
  }  
}

CountryDetails.contextType = CountryContext

export default CountryDetails

Make work/debugging faster

CountryContext.displayName = 'SelectedCountry'

context.provider app layout

Example of using multiple contexts

import React, { useContext } from 'react'
import { CountryContext, LanguageContext } from './contexts'


// using hook in stateless components
const Languages = () => {  
    const {
        selectedCountry: {
            languages = []
        }
    } = useContext(CountryContext)

    const {
        lang
    } = useContext(LanguageContext)

    return (
        <div>...
    )
}
// using Consumer component f.ex. in class components
<CountryContext.Consumer>
  {({ selectedCountry }) => (
    <LanguageContext.Consumer>
      {({ lang }) => {
          <div> ...
        }
      }
    </LanguageContext.Consumer>
  )}
 </CountryContext.Consumer>

Summary

React Context is a very approachable and helpful API for managing state over multiple components.

React Context is a very approachable and helpful API for managing state over multiple components.

It makes our work faster and easier by accessing data everywhere across the app.

Top comments (0)