React Context#
Updated for React 19.
TLDR on React Context#
Use Context for implicit, client-only, data distribution.
Context is an alternative props (which are explicit and available to both server and client components).
Context allows for component relationships similar to HTML elements <li>
and <ol>
.
Here, data applied to the parent, has an implicit impact on how children render.
<ol start="10"> <li>Tenth</li> <li>Eleventh</li> <li>Twelfth</li></ol>
Context makes a value available to all descendents.
<UserContext value={user}> <UserAvatar /> <UserName /></UserContext>
Those descendents must opt in to Context.
function UserAvatar() { let user = React.use(UserContext);
return <img src={user.avatar_url} />;}
Unlike HTML, Context does not require direct parent-child relationships.
Contexts can be sent and recieved “thru” intermediate elements, components, and context providers.
<OrgContext value="ACME Co."> <PersonContext value="Yakko"> <div class="my-layout"> <OrganizationBizCard /><!-- Yakko, ACME Co. --> </div> </PersonContext></OrgContext>
This doc is a guide for implementing Context in React.
Table of contents#
- Real-world React Context (a “shit” example)
- Context is a 3-part system: create, use, and provide
- Context is an alternative to props that is implicit
- Think of props like wired and Context like wireless (a mental model)
- Context is available to all descendents (indifferent to how deeply nested)
- Context is composable
- Context can not be sent “over the wire” (it’s not available to the server)
- Context is used to implement the compound components pattern
- Context can be used to implement distributed state management (with useReducer)
- Any JavaScript value can be be shared via Context
- You can set a default
value
when creating Context - Context can be used inline with the render prop pattern
- Context can cascade
- What is Legacy Context?
Real-world React Context (a “shit” example)#
Shit is fine word in my house. But my mom hates it. So I tell my kids to use another word when around grandma.
Here’s how I’d implement that with React Context.
// It's ok to say "shit" as a default expletivelet ExpletiveContext = React.createContext("shit");
// But context is important. Learn to account for it.function ContextualExclamation() { let word = React.use(ExpletiveContext);
return <span>Oh {word}!</span>;}
// When at Grandma's house, say "snap" insteadfunction AtGrandmasHouse() { return ( <ExpletiveContext value="snap"> <ContextualExclamation /> </ExpletiveContext> ));
// => <span>Oh snap!</span>
Context is a 3-part system: create, use, and provide#
Context is a 3-part system: create, use, provide.
Create context with React.createContext
.
let NameContext = React.createContext("Guest");
Use context with React.use
.
function ContextualGreeting() { let name = React.use(NameContext);
return <h1>👋 {name}!</h1>;}
Provide context by rendering the Context
with a value
prop.
<NameContext value="Chan"> <ContextualGreeting /></NameContext>
// => <h1>👋 Chan!</h1>
Context is an alternative to props that is implicit#
Context is most useful when many components require the same data.
Here’s an example:
<> <UserAvatar user={user} /> <UserTimeline user={user} /> <UserRelatedPosts user={user} /></>
These components may, in turn, also pass the same data to their children.
function UserRelatedPosts({ user }) { const related_posts = getRelatedPosts(user);
return ( <ul> {related_posts.map(post => ( <li> <span>{post.body}</span> <UserMiniProfile user={user} /> </li> ))} </ul> );}
With Context, the data is set once on the parent.
<UserContext value={user}> <UserAvatar user={user} /> <UserTimeline user={user} /> <UserRelatedPosts user={user} /></UserContext>
And descendent components opt into this data with use
.
function UserAvatar({ user }) { const user = React.use(UserContext);
return <img src={user.avatar_url} />;}
Think of props like wired and Context like wireless (a mental model)#
Props are like wires.
They “connect” data between components.
Like wires, the components have to be “touching”.
Meaning that components holding data have to render components that need it.
Context is like a wireless.
It sends a “signal” that is received by children.
Like wireless, components don’t need to be “touching” they only need to be “in range”.
Meaning that children of context can recieve the signal that context sends.
Context is available to all descendents (indifferent to how deeply nested)#
Every descendent/child of a Context provider can observe Context’s value.
function App() { return ( <UserContext value={user}> {/* UserContext can be received here… */}
<div className="app-layout"> {/* also here… */}
<div className="page-layout"> {/* and anywhere in this tree… */} </div> </div> </UserContext> );}
Context is composable#
Contexts can be chidren of other Contexts. Order doesn’t matter much unless it matters to your app.
let OrgContext = React.createContext();let PersonContext = React.createContext();
function App() { return ( <OrgContext value="ACME Co."> <PersonContext value="Yakko"> <UserOrgBizCard /><!-- Yakko, ACME Co. --> </PersonContext> </OrgContext> );}
function UserOrgBizCard() { const org_name = React.use(OrgContext); const person_name = React.use(PersonContext);
return ( <div className="business-card"> {person_name}, {org_name} </div> );}
Context can not be sent “over the wire” (it’s not available to the server)#
Context is only available to client components.
Server components cannot recieve context.
Context is used to implement the compound components pattern#
The “compound component” pattern describes components that are isolated but interdependent.
In HTML, li
and ol
are interdepedent. As are option
and select
.
This same interdependent relationship can be implemented using React Context.
import * as React from "react";import * as User from "./user";
function App() { const [editing, setEditing] = React.useState(false);
const [user, setUser] = React.useState({ name: "Guest", avatar_url: "https://example.com/avatar.png" });
return ( <User.Context value={user}> {isEditing ? <User.Form action={formData => { setUser({ name: formData.get('name') }); setEditing(false); }} /> : <> <User.Avatar /> <User.Name /> <button onClick={() => setEditing(true)}> Edit </button> </> } </User.Context> );}
import * as React from "react";
export const Context = React.createContext();
function Avatar() { const user = React.use(UserContext);
return <img className="user-avatar" src={user.avatar_url} />}
function Name() { const user = React.use(UserContext);
return ( <span className="user-name">{user.name}</span> );}
function Form({ action }) { const user = React.use(UserContext);
return ( <form className="user-form" action={action} > <input type="text" name="name" defaultValue={user.name} /> </form> );}
Context can be used to implement distributed state management (with useReducer)#
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 both distribute state and dispatch updates.
Here’s an minimum connection of the two.
import * as React from 'react';import * as ClickCount from './click_count';
function App() { const clickState = React.useReducer( ClickCount.reducer, ClickCount.initialState );
return ( <ClickCount.Context value={clickState}> <strong> <ClickCount.Show /> </strong> <br /> <ClickCount.IncrementAction /> </ClickCount.Context> );}
import * as React from 'react';
export const initialState = { count: 0 };
export function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; default: return state; }}
export const Context = React.createContext();
export function Show() { let [state] = React.use(Context);
return <>{state.count}</>;}
export function IncrementAction() { let [, dispatch] = React.use(Context);
return ( <button onClick={() => dispatch({ type: 'increment' })}> Increment count </button> );}
Any JavaScript value can be be shared via Context#
Context 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"]});
let MapAndSetContext = React.createContext( new Map([ [ 'Taylor Swift', new Set(['Tortured Poets Department', 'Midnights', 'Evermore']), ], ]));
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>);
You can set a default value
when creating Context#
Context can be initialized with a default value
.
When a component attempts to use
Context, but no corresponding Context
is found, a default value will be used.
let UserContext = React.createContext("Guest");
function UserGreeting () { let name = React.use(UserContext);
return <span>Hi {name}!</span>;}
let App = props => ( <div> <ContextGreeting /> {/* => Hi Guest! */}
<UserContext value="Bulbasaur"> <ContextGreeting /> {/* => Hi Bulbasaur! */} </UserContext> </div>);
Context can be used inline with the render prop pattern#
Context exposes a Consumer
component for inline context use. This allows context to be consumed without creating new components.
<SomeContext value="some value"> <SomeContext.Consumer> {value => <span>{value}</span>} </SomeContext.Consumer></SomeContext>
Here’s an example where multiple contexts are created and used.
const OrgContext = React.createContext();const PersonContext = React.createContext();
function App () { return ( <OrgContext value="ACME Co."> <PersonContext value="Yakko"> <OrgContext.Consumer> {organization => ( <PersonContext.Consumer> {person => ( <span> {person}, {organization} </span> )} </PersonContext.Consumer> )} </Organization.Consumer> </PersonContext> </OrgContext> )}
// => Yakko, ACME Co.
Context can cascade#
Consumers use the value from the nearest Context
.
Where none is present, the createContext
default value is used.
const MyContext = React.createContext("default");
function ShowContextValue() { const value = React.use(MyContext);
return <>{value}</>;}
function App() { return ( <> <MyContext value="outer"> <ShowContext /> {/* "outer" */}
<MyContext value="inner"> <ShowContext /> {/* "inner" */} </MyContext> </Provider>
<ShowContext /> {/* "default" */} </> );}
What is Legacy Context?#
Legacy Context refers to a set of APIs that were removed in React 19.
These include class-based component context.
Read the Legacy Context doc for more details.
© 2024 Michael Chan, Some Rights Reserved
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.