loading...

React Context

dianakw8591 profile image dianakw8591 ・3 min read

I'm almost two weeks into learning React, and while working on a lab that models Westworld's command center I realized that I was passing props down five levels deep! This process of prop drilling (vocab word of the day) struck me as a lot of extra typing, and therefore not terribly React-like. For example, information about my hosts was stored in state the top level in App, and passed down into my next components:

//In App
render(){
    return (
      <Segment id='app'>
        ...
        <Headquarters hosts={this.state.hosts} ... />
      </Segment>
    )
  }

where it was passed deeper...

// in Headquarters
<ColdStorage hosts={this.hostsInStorage()} ... />

and deeper...

// in ColdStorage
<HostList hosts={props.hosts} ... />

still deeper...

// in HostList
{props.hosts.map(host => {
        return <Host key={host.id} host={host} ... />
        })}

and finally down in the Host component the information is actually used.

That's a lot of repetitive typing for every prop I might need to send from App down to a Host. While there are multiple solutions to this problem, React Context offers one. Straight from the React docs:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Stated this way, Context offers a solution to the exact problem I encountered. So, onto adding Context.

In my Westworld lab, I want to create a Context that contains my Host data and makes it available to all of the children. The easiest way for me to do this with my current code is to create a new Context in App;

const hostContext = React.createContext({host: []});

I'm initially creating a Context with a default value of {host: []} because my host data is fetched asynchronously in componentDidMount in App. To make this Context available to all of App's children, I'll use React's Provider component, a property of the Context. I'll wrap all of App's children in <hostContext.Provider value={importantDataHere}> tags.
The value prop is what contains the information that I want passed down to all of my children components. In this case, I can assign value={hosts: this.state.hosts} where this.state.hosts now contains all of my fetched data. To recap, my App component will look like this:

...
const hostContext = React.createContext({host: []});
...

class App extends Component {

  state = {
    hosts: [],
    ...
  }

  componentDidMount() {
    fetch(HOSTS_URL)
    .then(resp => resp.json())
    .then(data => this.setState({
      hosts: data
    }))

  render(){
    return (
     <hostContext.Provider value={{hosts: this.state.host}}>
      <Segment id='app'>
        ...
        <Headquarters />
      </Segment>
     </hostContext.Provider>
    )
  }
}

This is great! But how do the children of App (and my new hostContext) access the value passed by the Provider? For class based components, the contextType property can be assigned to the given Context, and the value accessed using this.context:

class Headquarters extends Component {
...
const hosts = this.context.hosts;
...
}
Headquarters.contextType = hostContext;

Note that only one Context can be assigned to a class based component in this way.

For functional components, the hook useContext gives access in the same way that this.context does for class components, and no need to include any assignment of contextType:

const hosts = useContext(hostContext).hosts

There are plenty of pros and cons to Context and it's not the right choice for every situation, but it seems like a great tool to have in the toolbox!

Posted on by:

dianakw8591 profile

dianakw8591

@dianakw8591

Rock climber turned coder.

Discussion

markdown guide