React Context
🌱 This post is in the growth phase. It may still be useful as it grows up.
Contents
- A “Shit” Example
- Create, Consume, and Provide Context
- Provide
value
- Authoring and Modules
- Cascading Context
- Data Distribution, Not State Management
- A Mental Model for Context
useContext
Hookstatic contextType
Hook
A “Shit” Example
This is a shit example of Context. Shit because it uses “shit” as an illustration and because it’s simplistic.
We’ll get to the why after we cover the how.
”Shit” is a fine word
“Shit” is fine word in my house.
But my mom hates it.
I tell my kids: “when your at grandma’s house, use a different word.”
Let’s implement this scenario using React Context.
// 1. It's ok to say "shit" as a default.const ExpletiveContext = React.createContext('shit')
// 2. But use a word that's contextually appropriate.function ContextualExclamation() { let word = React.useContext(ExpletiveContext)
return <>Oh {word}!</>}
// 3. At grandma's house, use the word "snap" insteadfunction GrandmasHouse(props) { return ( <ExpletiveContext.Provider value="snap"> {props.children} </ExpletiveContext.Provider> )}
// 4. Something exciting happened. What do you say?function VisitToGrandmasHouse() { return ( <GrandmasHouse> <ContextualExclamation /> </GrandmasHouse> )}
// => Oh snap!
Prefer video?
Create, Consume, and Provide Context
Context is a 3-part system: create, use, provide.
Create
Context must first be defined.
Create context using React.createContext
.
let NameContext = React.createContext()
Create (with default value)
React.createContext takes take an optional. Define it using a sensible fallback
let NameContext = React.createContext('Guest')
Prefer video?
Use
Context is accessed with the React.useContext
hook.
Consumer
Provided functions get the Context’s value
as their first argument.
let NameContext = React.createContext("Guest");
function ContextGreeting() { let value = React.useContext(NameContext);
return <h1>👋 {value}!</h1>} }
// => <h1>👋 Guest!</h1>
In this example, value
is the default value used to create NameContext
.
So, how do we provide Context? I’m always glad you ask…
Prefer video? Watch along at learnreact.com.
Provide
Provider
is a component that takes a value
prop and makes it available to every component in the component tree below it.
let NameContext = React.createContext('Guest')
let ContextGreeting = () => ( <NameContext.Provider value="Michael"> <NameContext.Consumer> {(name) => <h1>👋 {name}!</h1>} </NameContext.Consumer> </NameContext.Provider>)
// => <h1>👋 Michael!</h1>
Providers
work where components are deeply nested.
let NameContext = React.createContext('Guest')
let ContextAwareName = () => ( <NameContext.Consumer> {(name) => <h1>👋 {name}!</h1>} </NameContext.Consumer>)
let NestedContextAwareName = () => <ContextAwareName />
let DeeplyNestedContextAwareName = () => ( <NestedContextAwareName />)
let ContextGreeting = () => ( <NameContext.Provider value="No Prop Drills"> <DeeplyNestedContextAwareName /> </NameContext.Provider>)
// => <h1> Welcome No Prop Drills!</h1>
Prop Drills not required for assembly.
Prefer video? Watch along at learnreact.com.
Provide value
A Context’s value
can take any shape.
Here are examples of valid Contexts values, using a default value
:
let StringContext = React.createContext('string')
let NumberContext = React.createContext(42)
let FunctionContext = React.createContext(() => alert('Context function'))
let ArrayContext = React.createContext([ 'some', 'array', 'elements',])
let ObjectContext = React.createContext({ aString: 'string', aNumber: 42, aFunction: () => alert('Context function'), anArray: ['some', 'array', 'elements'],})
value
can be complex structures like React Elements, class components, and function components.
let ReactElementContext = React.createContext( <span>React Element</span>)
let FunctionalComponentContext = React.createContext( (props) => <span>Function Component</span>)
let ClassComponentContext = React.createContext( class extends React.Component { render() { return <span>Class Component</span> } })
value
is required on Context Providers
Where a Context Provider
is used, the value
prop is required.
// NOPE<SomeContext.Provider></SomeContext.Provider>
// YEP!<SomeContext.Provider value="value is a required prop"></SomeContext.Provider>
What about the default value
given to createContext
?
The default value given to createContext
is used for Consumer
components without a Provider
.
Where Provider
s wrap their Consumer
s, all bets are off.
You must explicitly provide a value
.
let UserContext = React.createContext('Guest')
let ContextGreeting = () => ( <UserContext.Consumer> {(word) => <span>Hi {word}!</span>} </UserContext.Consumer>)
let App = (props) => ( <div> <ContextGreeting /> {/* => Hi Guest! */} <UserContext.Provider> <ContextGreeting /> {/* => Hi ! */} </UserContext.Provider> <UserContext.Provider value="Bulbasaur"> <ContextGreeting /> {/* => Hi Bulbasaur! */} </UserContext.Provider> </div>)
Prefer video? Watch along at learnreact.com.
Authoring and Modules
A Context’s Consumer
and Provider
components can be accessed in 2 ways.
The Examples above use JSX’ property access syntax. This is the style used in official documentation.
<SomeContext.Provider value="some value"> <Context.Consumer> {(value) => <span>{value}</span>} </Context.Consumer></SomeContext.Provider>
Above, you access the Provider
and Consumer
components through the Context object.
You may prefer to use object destructuring to assign Provider
and Consumer
components to local variables.
// Destructure your Context's Consumer and Providerlet {Consumer, Provider} = SomeContext
;<Provider value="some value"> <Consumer>{(value) => <span>{value}</span>}</Consumer></Provider>
Take care where multiple contexts are used.
let { Consumer: OrganizationConsumer, Provider: OrganizationProvider,} = React.createContext()
let {Consumer: PersonConsumer, Provider: PersonProvider} = React.createContext()
let App = () => ( <OrganizationProvider value="ACME Co."> <PersonProvider value="Yakko"> <OrganizationConsumer> {(organization) => ( <PersonConsumer> {(person) => ( <span> {person}, {organization} </span> )} </PersonConsumer> )} </OrganizationConsumer> </PersonProvider> </OrganizationProvider>)
// => Yakko, ACME Co.
Cascading Context
Context cascades.
Consumers use the value from the nearest Context.Provider
.
Where none is present, the createContext
default value is used.
let {Provider, Consumer} = React.createContext('global default')
function App() { return ( <> <Provider value="outer"> <Consumer> {(value) => <div>{value}</div> /* "outer" */} </Consumer>
<Provider value="inner"> <Consumer> {(value) => <div>{value}</div> /* "inner" */} </Consumer> </Provider> </Provider>
<Consumer> {(value) => <div>{value}</div> /* "global default" */} </Consumer> </> )}
Data Distribution, Not State Management
Context makes it possible to distribute data to every component in a component tree.
It’s used to distribute data, not manage state. That said, it provides the mechanism needed to state and updater functions managed by state containers.
Here’s an example of a stateful container that uses Context to distribute local state
and an update
function.
let StateContext = React.createContext()
class StateProvider extends React.Component { static defaultProps = { initialState: {}, }
update = (updater, done) => { this.setState( (prevState) => ({ state: typeof updater === 'function' ? updater(prevState.state) : updater, }), done ) }
state = { state: this.props.initialState, update: this.update, }
render() { return ( <StateContext.Provider value={this.state}> {this.props.children} </StateContext.Provider> ) }}
let App = () => ( <StateProvider initialState={{count: 0}}> <StateContext.Consumer> {({state, update}) => ( <div> <div>{state.count}</div>
<button type="button" onClick={() => update(({count}) => ({count: count + 1})) } > increment </button> </div> )} </StateContext.Consumer> </StateProvider>)
Modularazing Context
In the “real world”, you’ll likely expose Contexts via ES Modules.
import React from 'react'
let {Provider, Consumer} = React.createContext()
export {Provider, Consumer}
import React from 'react'
let {Provider, Consumer} = React.createContext()
export {Provider, Consumer}
Consumer
s can be imported to compose context-aware components.
import React from 'react'import {Consumer as PersonConsumer} from './person_context'import {Consumer as OrganizationConsumer} from './organization_context'
export function ContextBizCard() { return ( <OrganizationConsumer> {(organization) => ( <PersonConsumer> {(person) => ( <div className="business-card"> <h1>{person}</h1> <h3>{organization}</h3> </div> )} </PersonConsumer> )} </OrganizationConsumer> )}
Provider
s can be imported to contain and supply values to context-aware components.
import {Provider as OrganizationProvider} from './organization_context'import {Provider as PersonProvider} from './person_context'import {ContextBizCard} from './context_biz_card'
let App = () => ( <OrganizationProvider value="ACME Co."> <PersonProvider value="Yakko"> <ContextBizCard /> </PersonProvider> </OrganizationProvider>)
// => Yakko, ACME Co.
A Mental Model for Context
Props are like wires. Props are used to “connect” data between components. Like wires, the components have to be “touching”. Meaning that component thats hold data have to render components that need it.
Context is like a wireless — like infrared.
Context is used to send a “signal”.
Like wireless, components have to be “in range” — children of a Context.Provider
— to recieve the signal.
Components can observe that signal with a Context.Consumer
.
useContext
Hook
As of v16.8 React provides a Hooks API for consuming context.
The main difference between the Context.Consumer
component and the useContext
Hook is this:
The useContext
Hook requires a component boundary;
Context.Consumer
can be used inline.
Here’s a re-implementation of A “Shit” Example using hooks:
import React from 'react'
const ExpletiveContext = React.createContext('shit')
function ContextualExclamation() { let word = React.useContext(ExpletiveContext)
return ( <ExpletiveContext.Consumer> {word} </ExpletiveContext.Consumer> )}
function VisitGrandmasHouse() { return ( <ExpletiveContext.Provider value="poop"> <ContextualExclamation /> </ExpletiveContext.Provider> )}
static contextType
Hook
Class components can consume one Context directly using static contextType
.
This differs from Context.Consumer
in that it happens in the component definition and it can only consume one Context.
Access to context is done thru the component instance.
Here’s a re-implementation of A “Shit” Example using static contextType
:
import React from 'react'
const ExpletiveContext = React.createContext('shit')
class ContextualExclamation extends React.Component { static contextType = ExpletiveContext
render() { return ( <ExpletiveContext.Consumer> {this.context.word} </ExpletiveContext.Consumer> ) }}
function VisitGrandmasHouse() { return ( <ExpletiveContext.Provider value="poop"> <ContextualExclamation /> </ExpletiveContext.Provider> )}
© 2018 Michael Chan Some Rights Reserved
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.