React Context Basics
Starting with React 16.3 we get a great api to use in our apps - Context api. Until now, our components used to depend on their immediate parent to supply them props to function properly. Using React Context we can pass data down to our leaf components from an ancestor component directly without using the intermediate components as passthrough nodes for the same data. Thus having a lot cleaner code.
An example without React Context
// Header.old.js
import React, { Component } from "react";
class Header extends Component {
constructor() {
this.state = {
user: {
name: "Ash Sidhu",
}
}
}
render() {
const { name } = this.state.user;
return (
<div>
<Logo/>
<AppNav name={name}/>
</div>
)
}
}
const AppNav = ({name}) => (
<div>
<a href="/about">About</a>
<UserDetails name={name}/>
<a href="/logout">Log out</a>
</div>
)
const UserDetails = ({name}) => (
<div>
Logged in as {name}
</div>
)
Refactor to use React Context
Notice the redundant prop, name
in the AppNav
component above, it exists merely to be passed down to UserDetails
where it is being used to display the user's name. Outside of this contrived example, we may have several more layers between the smart component accessing app state and the dumb component consuming it. React Context comes to the rescue allowing the dumb component to use the state, bypassing all the middle layers between these two. Here's the same example refactored to use the new api.
// Header.new.js
import React, { Component, createContext } from "react";
const UserContext = createContext({name: ""});
class Header extends Component {
constructor() {
this.state = {
user: {
name: "Ash Sidhu",
}
}
}
render() {
return (
<div>
<Logo />
<UserContext.Provider value={this.state.user}>
<AppNav />
</UserContext.Provider>
</div>
)
}
}
const AppNav = _ => (
<div>
<a href="/about">About</a>
<UserDetails/>
<a href="/logout">Log out</a>
</div>
)
const UserDetails = _ => (
<UserContext.Consumer>
{({ name }) => (
<div>
Logged in as {name}
</div>
)
}
</UserContext.Consumer>
)
Explanation
First we create a context instance with a default value of {name: ""}
. The context consumer will use this value if there is none supplied to the provider. This is very handy when unit testing functional components which are context consumers.
Then we wrap the AppNav
component in UserContext.Provider
with a value prop. Note that I've wrapped user data under user
key in the state.
Caveat
An antipattern worth pointing out, would be storing the name in root level in the state and then passing the state object to the provider. It will result in the consumer being unnecessarily rerendered every time we change the state object's reference. More information about this can be found in the official docs here.
The final step is to use UserContext.Consumer
to wrap our context consuming component. Its child is always a functional component which receives the context as an argument and renders our desired component. In our case, UserDetails
will rerender every time the reference to this.state.user
will change.
Let me know if you found this useful. I will be publishing another post on context usage patterns with redux very shortly, so stay tuned.