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
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.
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'
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.