Knowing the direction of data flow through your components is very important when working with React as it enables you detect issues and fix things more easily. However with time your application might grow and managing multiplex views can get quite difficult. At this point, manually passing down common data in large component trees can get stressful. That’s where React’s Context API comes in. Context allows you pass data through the component tree without having to pass the props down manually at every level. It must be noted that React documentation stresses on the need not to implement context unless you are an experienced React developer and familiar with libraries such as Redux and MobX. In this post, I will explain what context is as well as the dangers of implementing it wrongly and how to safely use it.

So what’s Context?

Context is a React API that enables interaction with deeply rooted components. Let’s say a root component defines a list or holds some information that some other component up in the component tree is interested in. With context, the information can be passed through the component tree directly thus there is no need to pass the props down manually at every level. The official example in React’s documentation best explains how context works:

  class Button extends React.Component {
    render() {
      return (
        <button style={{background: this.props.color}}>
          {this.props.children}
        </button>
      );
    }
  }

  class Message extends React.Component {
    render() {
      return (
        <div>
          {this.props.text} <Button color={this.props.color}>Delete</Button>
        </div>
      );
    }
  }

  class MessageList extends React.Component {
    render() {
      const color = "purple";
      const children = this.props.messages.map((message) =>
        <Message text={message.text} color={color} />
      );
      return <div>{children}</div>;
    }
  }

In the example above, styling Button and Message requires that we we manually thread through the property color. With context, the approach to the problem is quite different as color can be passed through the tree spontaneously:

  import PropTypes from 'prop-types';
  class Button extends React.Component {
    render() {
      return ({
        this.props.children
      });
    }
  }
  Button.contextTypes = {
    color: PropTypes.string
  };
  class Message extends React.Component {
    render() {
      return ({
          this.props.text
        }
        Delete);
    }
  }
  class MessageList extends React.Component {
    getChildContext() {
      return {
        color: "purple"
      };
    }
    render() {
      const children = this.props.messages.map((message) => );
      return {
        children
      };
    }
  }
  MessageList.childContextTypes = {
    color: PropTypes.string
  };

In the second part of our example, a couple of methods were introduced via context API. Let’s take a look at these methods:

  • childContextTypes: A static property that enables you to declare the structure of the context object being passed to your component’s descendants. In many ways it is similiar to PropTypes but unlike PropTypes it is not optional.

  • getChildContext: A prototype method that returns the context object to pass down the component’s hierarchy. For every time the state changes or the component receives new props, this method will be called.

By adding childContextTypes and getChildContext to MessageList (the context provider), it is ensured that React passes the information down automatically and any component in the subtree which is Button in our case can access it by defining contextTypes. If contextTypes is not defined, then context will be an empty object.

Dangers of Using Context Improperly

Context can be referenced in the methods that are called when a component is being re-rendered:

  componentWillReceiveProps(nextProps, nextContext) { ... 
  }
  shouldComponentUpdate(nextProps, nextState, nextContext) { ... 
  } 
  componentWillUpdate(nextProps, nextState, nextContext) { ...
  } 
  componentDidUpdate(previousProps, previousContext) { ... 
  }

Now here’s where using context becomes a liability. shouldComponentUpdate bypasses the rendering of a part of the component tree. If props or state of a component are not changed in a meaningful way, it will return false from shouldComponentUpdate and descendants that make use of props and/or state won’t be updated. The CodePen demo below highlights what I’m explaining here:

In the demo above, shouldComponentUpdate and context are out of sync and this can clearly be seen once the You Can Go! button is clicked. Only You Can Go! gets the intended color update green, the motor items are not updated. The reason for this is that our MotorSport component inherits some abilities from PureComponent which includes implementing shouldComponentUpdate, one of these abilities includes MotorSport not re-rendering whenever it does not receive any new motor items. This ultimately causes the RulesTheme component inside MotorSport not to receive the new context with the updated color aqua. shouldComponentUpdate returns false therefore our MotorSport component and all its descendants fail to be updated. What are the implications of this?

  • MotorSport and all its descendants failure to be updated implies that every context provider and consumer in our application is broken. The larger the size of the application, the more frustrating this will be to debug.

  • This behavior shows that context is basically a global variable in the scope of a single React subtree. This makes our component more coupled and very dangerous to reuse outside the subtree

How to safely use Context

If you take a good look at the previous example, you will notice that where the problem began was when context was updated. With this in mind, here are two guidelines to go by in order to safely implement context:

  • Context should not change (it should be immutable).
  • A component should receive context only once, when it is being constructed.
  • Do not store state directly in context.

If these guidelines are followed, shouldComponentUpdate will not get in the way of context that needs to be passed on because the need for it to pass context to root components is eliminated. Again check out the previous demo, this time with the above listed guidelines followed:

For the sake of clarity, i will list what happened when the application is rebuilt following the guidelines:

  • Although MotorSport still extends PureComponent here, the motor items now get the color update change.

  • There was no need to alter the API of key components such as Race , MotorSport and RulesTheme.

  • RaceTheme contains an object Track. The function of Track is to hold the state of the application’s styling and also to emit events that allow RulesTheme to respond to future changes.

  • Track is passed through the component tree by RaceTheme. Although context is what powers that, after the initial time it is passed, context is no longer needed as other updates are generated by Track and not by creating a new context as in the previous example.

Conclusion

It cannot be overemphasized that context in React is an advanced and experimental feature with its API likely to change in future releases of React. Implementing context has been likened to using global variables to pass state through your application. In my opinion, if you still have to use context, use it sparingly, try to isolate your use of context to a small part of your application and avoid using the context API directly when possible so that it's easier to upgrade in future when the API changes.

Chris Nwamba

51 posts

JavaScript Preacher. Building the web with the JS community.