DEV Community

Cover image for Context in React
K (he/him)
K (he/him)

Posted on • Updated on

Context in React

Cover image by Rebecca Jackson on Flickr.

What

First, what is a context?

A context is a way to store and pass down data a React element tree without writing it into a prop every layer of the hierarchy. It consists of two components, a Provider and a Consumer.

A Provider stores some data that can be accessed via the Consumer somewhere in the tree below the Provider. So the Consumer has to be a descendant of the Provider.

A Consumer accesses the Provider data and makes it available to its children via a render prop. The nice thing being, a Consumer doesn't have to be a direct child of a Provider it can be anywhere in the tree below it.

Why

Sometimes you use data inside your application that is quasi global. Sometimes it's global to the whole application, sometimes just global to a screen or page, but it is used in many places.

For example, you want to use theme information in all of your UI components, or you want to make the data of the currently logged in user available to many components, or you have an API client that needs to be configured one time and than used all over your application.

Now you could make this data simply global, but this would get unwieldy rather quick. A context is a way to do this in an encapsuled way and since none of the elements between the Provider and the Consumer know about the context or its data, it also is another way to add dependency injection into your app and make it more resilient to change.

How

So how do you create a context? And how do you use it later?

The context API got rewritten not long ago for flexibility and ease of use. React provides a simple function to create a context.

const Context = React.createContext();
Enter fullscreen mode Exit fullscreen mode

This function returns an object with two attributes Provider and Consumer that contain the components which are needed to use this context later. 

A basic usage could look like this:

<Context.Provider value="context data">
  ...
  <Context.Consumer>
    {value => <p>{value}</p>}
  </Context.Consumer>
  ...
</Context.Provider>
Enter fullscreen mode Exit fullscreen mode

The Provider takes a value prop that becomes its state. The Consumer takes a render prop in form of children as a function. This function receives the current value as argument.

Often you have more complex data and a way to change this data down in the components that use it.

Here a more complex example:

const Context = React.createContext();
class A extends React.Component {
  state = { x: 1 };
  handleContextChange = x => this.setState({ x });
  render() {
    const contextValue = {
      data: this.state,
      handleChange: this.handleContextChange
    };
    return (
      <Context.Provider value={contextValue}>
        <B />
      </Context.Provider>
    );
  }
}
const B = props => <div><C /></div>;
const C = props => (
  <Context.Consumer>
    {({ handleChange, data }) => (
      <div>
        <button onClick={() => handleChange(2)}>Change</button>
        <D text={data.x} />
      </div>
    )}
  </Context.Consumer>
);
const D = props => <p>{props.text}</p>;
Enter fullscreen mode Exit fullscreen mode

We start by creating a Context.

Then we use it in component A which is the top of our hierarchy. The value for our Context.Provider is the state of A and a method of A that handles changes to this state. When the state changes, the Context.Provider gets a new value. A is also the storage of our state, the context just pipes it down the hierarchy.

In component C we use the Context.Consumer, it receives a function via its children render prop. If the value of the Context.Provider changes this function is simply called again and renders with the new value.

As you can see, component B which is between A and C is completely elusive to the whole context arrangement. It just renders component C indifferent about its implementation.

Also, component D and the button element don't know anything about context. They just get data and the change handler function passed via their props and can use them as any other prop. D wants the text it renders passed into its text prop instead of children and button is just a regular old button that executes everything passed into its onClick prop. So context is an extension of the dependency injection used in pure render props.

Because the pair of Provider and Consumer are created per createContext() call, you can even have multiple contexts. Every context is encapsuled and safe from actions of other contexts.

Conclusion

The new context API is much more flexible than the old one and works without prop-types and since it's now stable, you can finally use it without fear that it goes away soon.

It also extends the dependency injection concepts used in render props by letting you pass down state from a component to a deep ancestor of it without telling the in-betweens anything of it.

Top comments (2)

Collapse
 
stylecramper profile image
Matt Anderson

When you say, "So the Consumer has to be a ancestor of the Provider.", do you mean that the Consumer has to be a descendant of the Provider?

Collapse
 
kayis profile image
K (he/him) • Edited

Yes, lol. Sorry, English isn't my first language. Fixed it :)