TechFlip

React context API (Part 1) - Passing state to child components made easy

July 07, 2019 - 8 min read

So, you’ve heard about React’s context API but are scared to approach it because of the complexity of Redux. Well, my friend, this is a simple tutorial to get you started with the beauty that is context.

context example

Photo by Oskar Yildez on Unsplash

If you just want the example, skip to the example section. You can also find the repo here.

What is context?

React’s context API was built to make passing data to components easier. This could be done by a method called prop drilling. Here is a really good article about prop drilling by Kent C. Dodds. Prop drilling is still a good method, but, it is not very practical for a larger application.

Many have also turned to a tool called Redux. Redux is awesome and the implementation is very similar to how you would use context. Unfortunately, the learning curve is pretty steep and, for most applications, Redux is just overkill.

So, back to what is context? Context lets state data be passed down from the parent to child within the component tree via a Provider. Each child component can have a Consumer that will subscribe to the state changes from the parent component. Now that is awesome. A functional child component can subscribe to the parent’s state.

Why is context useful?

Context allows for data to be shared as global data within a component tree. This makes code much more readable. A child component directly subscribes to the top component state no matter where the child component lies within the component tree.

Remember prop drilling? Well, as component trees get bigger and bigger, the layers that the top state have to drill through becomes quite complex. It is often hard to find where in the component tree contains a bug. With context, each child component is simply subscribing to the top layer state. Forwarding props through layers does not become an issue in this case.

Simple Tutorial

Where’s the example already? Don’t worry. It’s coming up next!

Create the context. Let’s create a Context.js file. It will create the Provider and Consumer for us.

import { createContext } from "react"

const { Provider, Consumer } = createContext()

export { Provider, Consumer }

First, we need to import createContext from react. This will allow us to create our Context object. Then, we’ll create the Provider and Consumer. This is important. The Provider will provide (no pun intended… well, kind of) the state to the Consumer. Each Consumer will subscribe to updates from the Provider.

The Parent. Next, we’ll create the Parent component. This component will contain the Provider and the state.

import React, { Component } from "react"
import { Provider } from "./Context"
import Child from "./Child"
class Parent extends Component {
  state = {
    people: [
      { id: 0, name: "Bob", age: 24 },
      { id: 1, name: "Jack", age: 22 },
      { id: 2, name: "Jill", age: 26 },
    ],
  }

  render() {
    return (
      <Provider value={this.state}>
        <Child />
      </Provider>
    )
  }
}

export default Parent

The state is declared as people. People has three items. The Provider context object is then wrapped around a Child component. Pay attention to the provider.

<Provider value={this.state}>
  <Child />
</Provider>

Within the Provider component, value is set to {this.state}. Within the Child component, there is a Consumer that will subscribe to the value given by Provider. In this case that value is the state. NOTE: The Provider ALWAYS has to wrap a Consumer. It will not work if components with Consumers do not have a Provider.

The Child. Next, we’ll create the Child component.

import React from "react"
import { Consumer } from "./Context"
import Grandchild from "./GrandChild"

function Child() {
  return (
    <Consumer>
      {context => (
        <div>
          <h1>Child Component</h1>
          {context.people.map(person => {
            return (
              <p key={person.id}>
                Hi, I am {person.name} and I am {person.age} years old.
              </p>
            )
          })}

          <GrandChild />
        </div>
      )}
    </Consumer>
  )
}

export default Child

Okay, there’s quite a bit going on. Lets break it down. First, the Consumer context object is imported. Next, the entire component is wrapped by the Consumer context object.

<Consumer>
  {context => (
    <div>
      <h1>Child Component</h1>
      {context.people.map(person => {
        return (
          <p key={person.id}>
            Hi, I am {person.name} and I am {person.age} years old.
          </p>
        )
      })}

      <GrandChild />
    </div>
  )}
</Consumer>

Within Consumer is {context => ( ... )}. Remember the value=this.state from the Provider? Well, context within the Consumer is directly connected to the value. This is because it has subscribed to it. Let’s take a closer look at how the Consumer is using the context. NOTE: ‘context’ is an arbitrary name. It could be data, dog, blah, etc.

{
  context.people.map(person => {
    return (
      <p key={person.id}>
        Hi, I am {person.name} and I am {person.age} years old.
      </p>
    )
  })
}

Because context was passed through the consumer and context subscribes to the state of the Parent, we have access to people! All we have to do to grab the content is execute context.people What is happening here is the Child component is mapping through each item within people. Then it is displaying a sentence using the name and age from each people item.

Let me take a little detour real quick to hopefully save you some headaches. Let’s look at the Provider one more time.

<Provider value={state: this.state}>
  <Child />
</Provider>

Notice anything different? Instead of having value={this.state} it is now value={state: this.state}. You absolutely can do this. In fact, in the part 2 of this series, we will be doing this. If you want to pass multiple values from the Provider to the Consumer, (like the state AND a function), then the way that the Consumer calls the item is different.

Here is how the Child component would look if the Provider passed the state the way shown above.

{
  context.state.people.map(person => {    return (
      <p key={person.id}>
        Hi, I am {person.name} and I am {person.age} years old.
      </p>
    )
  })
}

Notice, now to use the value from the Provider, we must say context.state.people. It took me forever to figure this out. But, hopefully this helps!

Okay, let’s look at the GrandChild component.

import React from "react"
import { Consumer } from "./Context"

function GrandChild() {
  return (
    <Consumer>
      {context => (
        <div>
          <h1>Granchild Component</h1>
          {context.people.map(person => {
            return (
              <p key={person.id}>
                Hi, I am {person.name} and I am {person.age} years old.
              </p>
            )
          })}
        </div>
      )}
    </Consumer>
  )
}

export default GrandChild

The GrandChild. Hmm… Looks familiar. It’s pretty much the same as the Child component as far as functionality. The entire component is still wrapped with Consumer. This allows the state from the Parent to be passed down.

<Provider value={this.state}>
  <Child />
</Provider>

Remember this? This is in the Parent component. The Provider wraps around Child. Within Child is GrandChild. This means that the GrandChild has access to subscribe to the value given by the Provider. That is why it is able to access the state.

But, what if I don’t want to nest them? What if I just want two components to independently have access to the parent state? No Problem. Check out the code below.

<Provider value={this.state}>
  <ComponentOne />
  <ComponentTwo />
</Provider>

Then, simply wrap the contents within each component with a Consumer and you’re all set.

Okay, finally we can run our app. Here is what it will look like.

context

Context Example

The Child component is the first component that receives the state from the context provider. It then precedes to pass the state down to the Grandchild. As you can see, even the Grandchild component that was wrapped within the Child component has access to the Parent state.

Conclusion

So, we’ve learned how to use React’s context API with a simple app. State from the Parent is passed down to Children via a Provider. Consumers are used within the Child components to subscribe to the value passed in the Provider. Not so bad right? Stay tuned for part 2 where I’ll show you how to update the state of the Parent with a Consumer.

Resources


Alex ValdezAlex Valdez

Personal blog by Alex Valdez.
Talking all things technology and life.