Topic 9.2
Context: Passing Data Deeply
In one line
Context lets a component make a value available to everything below it without passing props: create it with createContext(defaultValue), provide it with <MyContext value={...}> (React 19) or <MyContext.Provider value={...}> (older code), and read it with useContext(MyContext), which returns the value from the nearest provider above.
Think of it like this
The Wi-Fi in a house. The router in the living room doesn't run a cable to every phone. Any phone in the house connects to the nearest router and gets the internet. If a room has its own extra router, phones in that room use that one instead. Context is React's Wi-Fi: a component broadcasts a value, and any component inside it can tune in, however deep, without cables (props) through every room.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Context
- A way to share a value with every component inside a part of the tree, without props.
- createContext
- The function that makes a context object. Its argument is the default value.
- Provider
- The wrapper that sets the context value for everything inside it:
<MyContext value={...}>. - .Provider
- The older way to write a provider,
<MyContext.Provider value={...}>. Still works in React 19. - useContext
- The hook a component calls to read the current value of a context.
- Default value
- The value
useContextreturns when there is no provider above the component at all. - Nearest provider
- The closest provider for that context above the reading component. Its value wins.
- Overriding
- Putting another provider for the same context deeper in the tree to change the value for that part.
Step by step
01Create the context
Put it in its own file and export it, so both the provider and the readers can import it. The argument is the fallback value.
import { createContext } from "react";
export const ThemeContext = createContext("light");02Provide a value
Wrap the part of the tree that needs it. Everything inside, at any depth, can now read "dark".
import { ThemeContext } from "./ThemeContext";
export default function App() {
return (
<ThemeContext value="dark"> {/* React 19 */}
<Page />
</ThemeContext>
);
}
// Older code (still works):
// <ThemeContext.Provider value="dark"> ... </ThemeContext.Provider>03Read it anywhere below
A component deep inside calls useContext. Page and anything between pass nothing.
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export function Button({ children }) {
const theme = useContext(ThemeContext);
return <button className={"btn-" + theme}>{children}</button>;
}04The nearest provider wins
useContext walks up from the reading component and stops at the first provider of that context. Nesting a second provider overrides the value for its subtree only.
05Build on the value above
A component can read a context and provide a new value for its children. Each Section reads the current level and provides level + 1, so headings pick h1, h2, h3 by themselves.
export function Section({ children }) {
const level = useContext(LevelContext);
return (
<section>
<LevelContext value={level + 1}>{children}</LevelContext>
</section>
);
}06Make the value change
Put the value in state. When state changes, the provider gets a new value and every reader under it renders with the new one.
const [theme, setTheme] = useState("light");
return (
<ThemeContext value={theme}>
<Page />
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Switch</button>
</ThemeContext>
);Try it yourself
- 1
Predict the badge
In the badge example, add
<Badge label="After everything" />as the last child of the outer<div>, outside every provider. Predict the colour before running. (Light: there's no provider above it, so it gets the default.) - 2
Start the headings at h2
In the headings example, wrap
<App />in<LevelContext value={1}>. Predict the logs. (Every heading moves down one level:h2,h3,h4,h4.) This is how a page can embed a component whose headings must start lower. - 3
Override for one card
In the language example, wrap a second
<Profile />in<LanguageContext value="ta">. Change the menu: which greeting follows it and which stays in Tamil?
Code & diagrams
No heading receives a level prop. Each `Section` reads the level from the nearest `Section` above it and provides one more. Move a `Heading` into a deeper `Section` and it becomes smaller by itself.
Preview
Console
Nothing logged yet.Expected console output
h1: React course
h2: Phase 9
h3: Topic 9.2: Context
h3: Topic 9.3: PitfallsEach badge takes the value of the closest provider above it; the first has none, so it gets the default. In React 19, `ThemeContext.Provider` is the same object as `ThemeContext`, which is why both syntaxes behave identically. (The module's last line runs before React's first render, so it prints first.)
Preview
Console
Nothing logged yet.Expected console output
Provider is the context itself: true
No provider above -> light
Inside dark -> dark
Inside sepia (overrides dark) -> sepia
Older .Provider syntax -> blue
Back in dark -> darkPick a language. `Greeting` is three components below the provider, and nothing in between passes the language. When the provider's value changes, React re-renders the readers.
Preview
Console
Nothing logged yet.// 1. Hook (React 16.8+): what you'll write.
const theme = useContext(ThemeContext);
// 2. use (React 19): like useContext, but may be called inside if/loops (Topic 14.4).
if (showTheme) {
const theme = use(ThemeContext);
}
// 3. Render-prop Consumer (older code, before hooks).
<ThemeContext.Consumer>
{(theme) => <button className={"btn-" + theme}>Save</button>}
</ThemeContext.Consumer>
// 4. Class components: one context through static contextType.
class SaveButton extends React.Component {
static contextType = ThemeContext;
render() {
return <button className={"btn-" + this.context}>Save</button>;
}
}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A provider without a value
In the badge example, change <ThemeContext value="sepia"> to <ThemeContext> (no value).
Break #2
Reading the context you provide in the same component
Write function App() { const theme = useContext(ThemeContext); return <ThemeContext value="dark"><p>{theme}</p></ThemeContext>; }.
Myth vs fact
Myth
The default value is used whenever the provider's value is missing.
Fact
It's used only when no provider exists above. A provider with value={undefined} gives undefined.
Myth
React 19 removed <Context.Provider>.
Fact
It still works and is the same object as the context. The new <Context value> form is preferred, and .Provider is planned to be deprecated later.
Myth
Context is a state manager.
Fact
Context only transports a value down the tree. The state still lives in a component (useState/useReducer) that passes it to the provider.
Myth
Every component between the provider and the reader re-renders when the value changes.
Fact
React renders the readers of that context. Components in between render only for their own reasons (their parent rendered, their state changed).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Under the hood, a provider pushes its value onto a per-context stack while React renders its subtree and pops it afterwards.
useContextreads the top of that stack, which is why the nearest provider wins and why lookups are cheap: there's no walking up the tree at read time. - ▸
When a provider's value changes (compared with
Object.is), React searches the subtree below the provider for components whose dependency list includes this context and schedules them to render. This works even if amemocomponent (Topic 16.3) between them skips rendering: context goes through memo. - ▸
createContextreturns an object; in React 19Context.Provider === ContextandContext.Consumeris a separate object with a_contextlink. Giving the context adisplayName(ThemeContext.displayName = "Theme") changes its label in React DevTools. - ▸
Context crosses portals (Topic 8.5): a modal rendered into
document.bodywithcreatePortalstill reads providers from where it sits in the React tree, not the DOM tree. It does not cross separatecreateRootroots; each root needs its own providers.
Remember this
- 1
Context is a React feature for sharing a value with a whole part of the component tree. It has three parts. 1. Create it once, outside any component:
const ThemeContext = createContext("light"). The argument is the default value. 2. Provide it: wrap part of the tree in<ThemeContext value="dark">…</ThemeContext>. That wrapper is called a provider. 3. Use it: any component inside callsconst theme = useContext(ThemeContext)and gets"dark". The components in between pass nothing. - 2
Two ways to write the provider. Since React 19 the context object itself is the provider:
<ThemeContext value="dark">. Before React 19 you had to write<ThemeContext.Provider value="dark">, and nearly all existing code, tutorials and libraries still do. Both work in React 19 and mean exactly the same; the React team plans to deprecate.Providerin a future version. You'll write the new form and read both. - 3
The nearest provider wins.
useContextlooks up the tree from the component that calls it and takes the value of the closest provider for that context. Providers can be nested: an outer<ThemeContext value="dark">for the page and an inner<ThemeContext value="light">around one panel. Everything inside the panel sees"light", everything else sees"dark". That's how you override a context for one part of the app. A component can also read a context and provide a new value based on it, such aslevel + 1, so each nested section knows how deep it is. - 4
The default value (the argument to
createContext) is used only when there is no provider above at all. It's a fallback, useful for tests and for components rendered outside the app's providers. It is not used when a provider exists but itsvalueisundefined: thenuseContextreturnsundefined. Many teams usenullas the default and throw a helpful error from a custom hook when it's missing (Topic 9.6). - 5
When the value changes, readers update. A provider's
valuecan come from state:<ThemeContext value={theme}>. Whenthemechanges, React finds every component that calleduseContext(ThemeContext)under that provider and renders it again, even if the components in between didn't render. That's how a theme switch can recolour a button deep in the tree. Topic 9.3 looks closely at the cost of this. - 6
Rules.
useContextis a hook: call it at the top level of a component or custom hook (Topic 4.3). A component can't read a provider that it renders itself:useContextlooks above the calling component, so the value it gets comes from a provider higher up (or the default). Create contexts at module level, usually in their own file, and export them: callingcreateContextinside a component would make a brand-new context on every render. In React 19 you can also read context withuse(ThemeContext), which, unlikeuseContext, may be called insideifand loops (Topic 14.4). Old class components read it withstatic contextTypeor<ThemeContext.Consumer>.
Explain it without notes
What are the three steps to use context? Show the code.
What's the difference between <ThemeContext value> and <ThemeContext.Provider value>?
When does useContext return the default value passed to createContext?
How do nested providers work? Give a real use for overriding a context.
Practice
Create a UserContext, provide { name: "Asha" } from App, and show Hi, Asha in a Navbar two levels down without passing props.
Make a SizeContext with default "medium". Render three <Box> components: one outside any provider, one inside <SizeContext value="small">, and one inside a nested <SizeContext value="large">. Each box shows its size.
Trade-offs
- ↔
Context removes props from the middle of the tree, but it hides the dependency: you can't see from a component's props that it needs a theme. Keep the number of contexts small and give each a custom hook with a clear name.
- ↔
A component that reads context only works inside the right provider, so it's less portable and needs a wrapper in tests (Topic 17.6). Props make a component self-contained.
Done when you can
I can create a context, provide a value and read it with
useContext.I can read and write both
<Ctx value>(React 19) and<Ctx.Provider value>.I can explain when the default value is used and when it isn't.
I can override a context for one part of the tree and build on the value above.
I know a component can't read a provider it renders itself.