Command Palette

Search for a command to run...

Hectal
PHASE 9Intermediate ~24 min· topic 2 of 6

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 useContext returns 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.

ThemeContext.jswhole filejsx
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".

App.jsxwhole filejsx
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.

Button.jsxwhole filejsx
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.

The nearest provider winsdiagram
Rendering diagram…

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.

Section.jsxwhole filejsx
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.

App.jsxwhole filejsx
const [theme, setTheme] = useState("light");
return (
  <ThemeContext value={theme}>
    <Page />
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Switch</button>
  </ThemeContext>
);

Try it yourself

  1. 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. 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. 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

Headings that know their level (read, then provide level + 1) New tab

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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

h1: React course
h2: Phase 9
h3: Topic 9.2: Context
h3: Topic 9.3: Pitfalls
Default value, nearest provider and overriding React 19+ New tab

Each 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

Press Run to see the page.

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 -> dark
A changing value reaches deep readers New tab

Pick 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

Press Run to see the page.

Console

Nothing logged yet.
Reading context in older codejsx
// 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).

terminal
$ Console
── what you'll see ──
Provider is the context itself: true
No provider above -> light
Inside dark -> dark
The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?
Inside sepia (overrides dark) -> undefined
Older .Provider syntax -> blue
Back in dark -> dark

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>; }.

terminal
$ Preview
── what you'll see ──
light

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. useContext reads 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 a memo component (Topic 16.3) between them skips rendering: context goes through memo.

  • ▸

    createContext returns an object; in React 19 Context.Provider === Context and Context.Consumer is a separate object with a _context link. Giving the context a displayName (ThemeContext.displayName = "Theme") changes its label in React DevTools.

  • ▸

    Context crosses portals (Topic 8.5): a modal rendered into document.body with createPortal still reads providers from where it sits in the React tree, not the DOM tree. It does not cross separate createRoot roots; each root needs its own providers.

Remember this

  1. 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 calls const theme = useContext(ThemeContext) and gets "dark". The components in between pass nothing.

  2. 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 .Provider in a future version. You'll write the new form and read both.

  3. 3

    The nearest provider wins. useContext looks 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 as level + 1, so each nested section knows how deep it is.

  4. 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 its value is undefined: then useContext returns undefined. Many teams use null as the default and throw a helpful error from a custom hook when it's missing (Topic 9.6).

  5. 5

    When the value changes, readers update. A provider's value can come from state: <ThemeContext value={theme}>. When theme changes, React finds every component that called useContext(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. 6

    Rules. useContext is 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: useContext looks 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: calling createContext inside a component would make a brand-new context on every render. In React 19 you can also read context with use(ThemeContext), which, unlike useContext, may be called inside if and loops (Topic 14.4). Old class components read it with static contextType or <ThemeContext.Consumer>.

Explain it without notes

01

What are the three steps to use context? Show the code.

02

What's the difference between <ThemeContext value> and <ThemeContext.Provider value>?

03

When does useContext return the default value passed to createContext?

04

How do nested providers work? Give a real use for overriding a context.

Practice

01

Create a UserContext, provide { name: "Asha" } from App, and show Hi, Asha in a Navbar two levels down without passing props.

02

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.