Content Outline
What is a hook?
A hook is a special function which lets you use functional components without writing ES6 class components. To appreciate the usefulness of useContext
hook, it is important to understand the problem it is trying to solve. In the sections below, we discuss how context
API is used in both class and functional components.
Prop drilling
In React, data can be passed from a component to its descendants primarily via props. It is not a big deal to pass props from a parent component to its descendants one or two levels deep, but it becomes a mess if components are deeply nested. This problem is illustrated in the code below.
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <ChildComponent name={"Joe"} />;
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <GrandChildComponent name={this.props.name} />;
}
}
class GrandChildComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <GreatGrandChildComponent name={this.props.name} />;
}
}
class GreatGrandChildComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1> Hello {this.props.name} </h1>;
}
}
You can see how props have to be passed through all intermediate components from App
in order to get to GreatGrandChildComponent
. This, according to Kent C. Dodds, is called prop drilling. Prop drilling is undesirable if the intermediate components have no use for the data. Your goal is to have the data sent to a specific component within the component tree. context
API was developed to address this problem. It helps in passing data from parent component to child components without drilling through successive descendants via props.
How to use context API in class components
Inorder to use context API in class components, you go through the following steps
- Create an instance of context using React.createContext
- Wrap children components of provider (component which dispatches data ) inside context.Provider
- Pass data via value attribute of context.Provider component
- Access data passed from context.Provider from any descendant of parent component via context.Consumer
Create an instance of context
In class components, you can use createContext
to create an instance of context
. createContext
takes context as an argument and returns an instance of the context. Context is an object whose value
property holds the data you want to pass. You can also create an instance of context without passing context(data) to createContext
. In that case you will pass data via value attribute of context.Provider
as explained in the subsequent sections.
import React from "react";
const myContext = React.createContext();
Alternatively, you can also import createContext
instead of using React.createContext()
like:
import React, { createContext } from "react";
const myContext = createContext();
A call to createContext
returns an object which in the case above is assigned to the variable myContext
. You can give a meaningful name to instance of createContext
instead of calling it myContext
One of the properties of myContext
object is Provider
whose usage is explained below.
Wrap children of parent component (provider of data) inside context.Provider
myContext
has a Provider
property which can be used inside component which provides data by wrapping siblings of the parent component in it. To illustrate this, let us go back to the previous example. If you want to pass data from App
component down the component tree to any of the descendant components, then App
is the provider of the data and is a parent to the components below it in the component tree. Simply wrap children of App
inside context.Provider
. This is illustrated in the code below.
import React from "react";
const myContext = React.createContext();
class App extends React.Component {
constructor(props) {
super(props);
this.state = { name: "John Doe" };
}
render() {
return (
<myContext.Provider value={{ ...this.state }}>
<ChildComponent />
</myContext.Provider>
);
}
}
Pass data via value attribute of context.Provider component
myContext.Provider
has an attribute called value
which is used for passing data.
In the code above, notice how React.createContext
has been invoked. Its invocation returns a context object which is stored in myContext
. In the render
method of the component (in this case App
) which provides the data to be passed down the component tree , you wrap the child component in myContext.Provider
and pass the data via value
attribute of myContext.Provider
. This way, any component which is a descendant of App
can access the data as explained below.
Access data passed from context.Provider from any descendant of parent component (App) via context.Consumer
In the sections above, we looked at how to create context and pass data down the component tree via context.Provider
. Let us look at how descendants of App
in the component tree can access the data passed. myContext
, context object returned by createContext
has a property called Consumer
which you can use to access the data. In the code below we assume GrandChildComponent
wants to use data which we passed from App
.
class GrandChildComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<myContext.Consumer>
{context => <h1> {context.name} </h1>}
</myContext.Consumer>
);
}
}
Notice the child of myContext.Consumer
is a function. The function takes the context
(in this case myContext
) as argument and returns JSX which renders the data passed. This can be done in any component which is a descendant of component from which data was passed. This helps to avoid prop drilling. Though context
API solves problem of drilling components to pass data around, it introduces complexity in myContext.Consumer
because a function has to be passed as its child. If you want to avoid this complexity, you can use useContext
. It should be noted that useContext
is a hook, therefore its use is restricted to functional components.
useContext hook
In the sections above, we looked at how context
API is used in class components. Dispatching context
in both class
and functional
components is the same. The difference is in the way context
is consumed. To use data passed from a parent
component via context
, you use context.Consumer
component in ES6 classes. A function is passed as a child of context.Consumer
. The function takes context
as an argument and returns JSX which renders the data passed via context.
In functional components on the other hand, useContext
hook takes a context object (object returned by React.CreateContext
) as an argument and returns the object passed via value
prop of Context.Provider
. The current context value is determined by the value
prop of the nearest Context.Provider
in the component tree in case a component has multiple ancestors dispatching data via Context.Provider
.
It should be noted that a component which renders data passed via context
API will always re-render when the context value changes. The code below is the functional component equivalent of context.Consumer
.
const myContext = React.useContext(context);
You can now access the data passed as value
attribute of context.Provider
using myContext
.
Thanks for reading this article till the end. If you find it informative, you can share it on Twitter. Others might find it useful too. In case you notice anything technically inaccurate, leave a comment below.
Top comments (1)
Can you please do correction in " Pt. 4 Access data passed from context.Provider from any descendant of parent component via context.Consumer " ? context.Consumer in Class component and useContext in functional component