Mark Pollmann
BlogBooksAbout

The old vs new React Context API

June 15, 2018 time to read: 4 minutes

With React 16.3 a new Context API will be introduced, making the old, experimental Context API obsolete. This marks a good time to take a look at the old and the new, what changes and why context is even needed in the first place.

What is Context?

I'm sure you've had the problem of a component needing some data from way up the component tree. Without using a state-management library like Redux the only solution seems to be prop drilling,passing the data down each component in-between the data provider and the data receiver, which looks something like this:

<Provider {...props}>
    <ComponentI {...props}>
        <ComponentDont {...props}>
            <ComponentNeed {...props}>
                <ComponentThis {...props}>
                    <Receiver {...props}>

This sucks.

React provided a experimental way to solve this problem with the context API. Let's see how it works.

The old context API

The idea is for the Provider to define a plain object from which the Receiver can select properties they are interested in.

Provider API

Let's say we have a color that we want to pass down. First we define a getChildContext() function that returns the plain object containing the data we want to pass down.
Then we define childContextTypes, like we would define prop types. This is what it looks like:

class MyProvider extends React.Component {
  getChildContext() {
    return { color: "green" }
  }

  render() {
    return /* ... */
  }
}

MyProvider.childContextTypes = {
  color: PropTypes.string,
}

Receiver API

The Receiver selects which of the properties of the context object he wants with the contextTypes property, analogously to the childContextTypes property of the Provider. Then these properties are made available on the class' context:

class MyReceiver extends React.Component {
  render() {
    const { color } = this.context
    return `The color is ${color}`
  }
}

MyReceiver.contextTypes = {
  color: PropTypes.string,
}

Usage examples

Even though the React team was quite clear that it's not a stable API, many libaries made use of it. Two quick examples:

react-router

React-Router makes the router property available in its context. See this short code snippet from the react-router source code:

class Router extends React.Component {
  static propTypes = {
    history: PropTypes.object.isRequired,
    children: PropTypes.node
  };

  static contextTypes = {
    router: PropTypes.object
  };

  static childContextTypes = {
    router: PropTypes.object.isRequired
  };

  getChildContext() {
    return {
      router: {
        ...this.context.router,
        history: this.props.history,
        route: {
          location: this.props.history.location,
          match: this.state.match
        }
      }
    };
  }
  /* ... */

react-redux

React-redux makes the redux store available to react components. For this it uses the Provider component as the Parent on the one hand as well as the connect function that wraps components in a Higher-Order Component with the redux state made available to Child components. See these code snippets from the react-redux library source code:

inside Provider.js:

export function createProvider(storeKey = "store", subKey) {
  const subscriptionKey = subKey || `${storeKey}Subscription`

  class Provider extends Component {
    getChildContext() {
      return { [storeKey]: this[storeKey], [subscriptionKey]: null }
    }

    /* ... */

    render() {
      return Children.only(this.props.children)
    }
  }

  /* ... */

  Provider.propTypes = {
    store: storeShape.isRequired,
    children: PropTypes.element.isRequired,
  }
  Provider.childContextTypes = {
    [storeKey]: storeShape.isRequired,
    [subscriptionKey]: subscriptionShape,
  }

  return Provider
}

inside connectAdvanced.js:

/* ... */
  const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
  }
  const childContextTypes = {
    [subscriptionKey]: subscriptionShape,
  }
/* ... */
  getChildContext() {
        const subscription = this.propsMode ? null : this.subscription
        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
      }
/* ... */

The new API

Things are going to be quite a bit simpler. Let's look at an example:

An Example

const BananaContext = React.createContext("hello")

class App extends React.Component {
  render() {
    return (
      <BananaContext.Provider value="hola">
        <BananaContext.Consumer>
          {context => <div>{context}</div>}
        </BananaContext.Consumer>
      </BananaContext.Provider>
    )
  }
}
// Prints hola

Let's look at the individual parts.

React.createContext()

React.createContext() creates an object that exposes two objects: A Provider and a Consumer. It takes a default argument that will get used if the Consumer is not wrapped in a corresponding Provider. Like this:

const BananaContext = React.createContext("hello")

class App extends React.Component {
  render() {
    return (
      <BananaContext.Consumer>
        {context => <div>{context}</div>}
      </BananaContext.Consumer>
    )
  }
}
// Prints hello

The default argument can of course also be an object if you need to provide more than one value.

Provider

The Provider provides the value in its appropriately name value prop. A more interesting value would be something from its internal state like so: value={this.state.someValue}.

Consumer

The Consumer takes a function as a child with the context provided as its only parameter. The function as a child pattern makes it flexible in the way how to process the given context. I'll definitely write a post about this pattern since it's pretty awesome.

Conclusion

The new context API is definitely easier to understand and reason about. It's finally a first class citizen in the React world and I'm looking forward to use it in my projects.