Prop drilling can be a good thing, and it can be a bad thing. Thankfully, there are better ways to do it with the Context API.
3 min read
·
By Emil Døhlen Hansen, Svein Petter Gjøby
·
December 15, 2018
In React, data is passed from parent to child with the help of props. This works fine most of the time. However, it can be cumbersome in certain situations where props are required by many components within an application. Svein Petter spends most of his days working for an international company that supports multiple languages. Various translations are required through the application in many different components.
Context provides a way to easily share values, like translations, between components without explicitly passing props from parent to child. This is useful for values that are considered global such as themes, translations or data about an authenticated user.
In this article, we will show you three different ways to use the context API and display your favourite reindeer.
const Reindeer = ({ name, speed }) => (
<>
<p>Name: {name}</p>
<p>Speed: {speed}</p>
</>
);
This method is used to create the context. It returns an object that contains a Provider and a Consumer. When React renders a component that consumes this context the value is read from the nearest matching Provider above in the component tree.
const ReindeerContext = React.createContext();
It is possible, and often desirable, to have multiple contexts in your React application.
Above we mentioned that the object returned from React.createContext()
contains a Provider. The Provider is the source of truth and allows consuming components to read the context value and subscribe to changes. It accepts a value
prop that is used to specify the value of the Provider.
When the value of the Provider changes, all the descendant consuming components will re-render. Hence, the descendant consumers are updated even when an ancestor decide not to re-render.
const App = () => (
<ReindeerContext.Provider
value={{
name: "Rudolph",
speed: 42,
}}
>
{/* Some awesome components */}
</ReindeerContext.Provider>
);
The context object also contains a Consumer. The Consumer is a component that lets you subscribe to context changes. As you can see below it requires a function as its child, and the context value can be retrieved by utilizing the render prop pattern. The context value is given as an argument to this function (the child component) and is equal to the value prop of the nearest matching Provider component.
const ReindeerWithConsumer = () => (
<ReindeerContext.Consumer>
{(context) => <Reindeer name={context.name} speed={context.speed} />}
</ReindeerContext.Consumer>
);
If you're using a class component and only need values from a single context, you can assign the contextType
class property to your class. This allows you to read the value from this.context
and you don't need to write RandomContext.Consumer
in your render method.
class ReindeerWithContextType extends Component {
contextType = ReindeerContext;
render() {
const { name, speed } = this.context;
return <Reindeer name={name} speed={speed} />;
}
}
Let's be honest. Repeatedly writing <RandomContext.Consumer>
and using the render props pattern isn't much better than passing props around your application. To avoid this you can create a higher order component (HOC) that assigns the desired values from the context to your component.
import React from "react";
import { ReindeerContext } from "../contexts/reindeer-context";
const withReindeer = (Component) => (props) => (
<ReindeerContext.Consumer>
{(context) => (
<Component {...props} name={context.name} speed={context.speed} />
)}
</ReindeerContext.Consumer>
);
export default withReindeer;
Awesome! Now we can enhance our component with withReindeer
, and name
and speed
will be available as props.
const ReindeerWithContext = withReindeer(Reindeer);
To play around with a working example of the snippets in this article, smash the button below.
Rudolph the Red-Nosed Reindeer. 🎵 🎅