THIS IS DRAFT AF. READ WITH CAUTION. open an issue to ask for for additional sections
This page is a supplement to the React doc on Context—found here.
It's a collection of my motions that helped me understand Context. Your mileage may vary.
This thing was much worse (read "wrong") before Dan Abramov's review. Thanks Dan for your patience, empathy, and clarity.
This is a shit example of Context. Shit because it uses "shit" as an illustration and because it's simplistic.
Don't worry, we'll get to the why after we cover the how.
My kids are allowed tasteful expletives. "Shit" is fine word in my house. But my mom hates the word.
I tell Rock—my 7 year old—"there's nothing wrong with the word 'shit'. Mr. Roger's loved the word. Some people hate it. Your grandmother is one of those people. Don't say "shit" around her. Use 'poop' instead."
"Who is Mr. Rogers?", he asks.
I think he gets it.
Let's implement this scenario of "shit", Rock, and grandma's house using React's Context API (>= v16.4).
// It's ok to say "shit" as a default.
let ExpletiveContext = React.createContext("shit");
// Some people don't like the word "shit".
// Use a different word, when in their company.
// It's good manners.
let ContextualExclamation = () => (
<ExpletiveContext.Consumer>
{word => <span>Oh {word}!</span>}
</ExpletiveContext.Consumer>
);
// Grandma *hates* the word "shit".
// Say "poop" at Grandma's house.
let GrandmasHouse = props => (
<ExpletiveContext.Provider value="poop">
{props.children}
</ExpletiveContext.Provider>
);
// What do you say when anything bad or exciting happens?
let VisitToGrandmasHouse = () => (
<GrandmasHouse>
<ContextualExclamation />
</GrandmasHouse>
);
// => Oh poop!
Prefer video? Watch along at learnreact.com.
Context is a 3-part system: Create, Consume, Provide.
Create context using React.createContext
.
import React from "react";
let NameContext = React.createContext();
This function takes an argument.
let NameContext = React.createContext("Guest");
NameContext
comprises two components, Consumer
and Provider
.
Let's dive into the Consumer
.
Prefer video? Watch along at learnreact.com.
Consumer
is a component that takes a function as children.
Provided functions get the Context's value
as their first argument.
let NameContext = React.createContext("Guest");
let ContextGreeting = () => (
<NameContext.Consumer>
{value => <h1>👋 {value}!</h1>}
</NameContext.Consumer>
);
// => <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.
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.
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 ProvidersWhere 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>
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.
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 Provider
let { 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.
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>
</>
);
}
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>
);
In the "real world", you'll likely expose Contexts via ES Modules.
// person_context.js
import React from "react";
let { Provider, Consumer } = React.createContext();
export { Provider, Consumer };
// organization_context.js
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.
// app.js
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.
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
HookAs 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
HookClass 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.