skip to content

useContext

You will learn the consuming side of context as an API: createContext's default value, how lookup walks up to the nearest provider, and the React 19 shorthand where the context object is itself the provider. Interviewers ask what happens with no provider in the tree, and the honest answer — you silently get the default — is the whole point.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In React, what does the argument passed to createContext(defaultValue) actually do, and what does useContext(MyContext) return when no matching provider is mounted above the component?

level: juniorimportance: must knowfreq 72%

answer

  1. only one situation uses it
  2. walk up, find nothing, fall back
  3. not an initial value
  4. undefined from a provider still wins
  5. silent, never an error

basics

~20 s

createContext's argument is a fallback used only when no matching provider sits above the consuming component. useContext then returns it silently instead of throwing. It is not an initial value, and a mounted provider never falls back to it.

solid answer

~50 s

`createContext(defaultValue)` returns a context object, and the `defaultValue` you pass is stored on it as a fallback for exactly one case: a component calls `useContext(MyContext)` and React walks up the tree without finding a provider for that same context object. In that case React returns the default and renders normally — there is no warning and no error, which is why a forgotten provider shows up as a component quietly rendering the wrong thing rather than as a crash. Two things people get wrong: the default is not an initial value that a provider later replaces on the same subscription, and it does not apply when a provider is present but passes `value={undefined}` — then consumers get `undefined`. Practically, pick a default that is either genuinely usable standalone or obviously wrong, like `null`, so the missing-provider case is easy to spot.

code

jsx · 18 lines
jsx
import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function Label() {
  return <span>{String(useContext(ThemeContext))}</span>;
}

export default function App() {
  return (
    <>
      <Label />
      <ThemeContext value={undefined}>
        <Label />
      </ThemeContext>
    </>
  );
}

go deeper

for a junior

Be able to say in one line that the createContext argument is used only when no provider is above the component, and that React returns it quietly instead of erroring.

for a middle

Explain the lookup that produces it: React walks up from the consumer to the nearest provider of the same context object, and the default is what you get when that walk finds nothing. Be precise that a provider supplying undefined overrides the default.

for a senior

Show the debugging angle. A component quietly rendering signed-out or default-themed UI is the signature of a misplaced provider, and you should be ready to say how you pick a default that makes that failure loud instead of plausible.

for a principal

Own the convention. Decide across a codebase whether contexts default to a usable value or to a null tripwire, and be able to argue the tradeoff between components that render standalone in isolation and failures that must never ship silently.

## What createContext gives you `createContext(defaultValue)` returns a **context object**. That object is not a value store; it is a key. React uses its identity to match a consumer to a provider. The object exposes `.Provider`, exposes the legacy `.Consumer` render-prop reader, and in React 19 the object itself can be rendered directly as a provider (`<ThemeContext value={theme}>`). The `defaultValue` argument is captured on that object once, at module evaluation time. It is never re-read, never re-computed, and never updated. If you write `createContext(new Date())` the default is the date at module load, forever. ```jsx const ThemeContext = createContext('light'); ``` ## How the lookup works, and where the default enters When a component calls `useContext(ThemeContext)`, React looks upward from that component through its ancestors for the nearest provider created from **the same context object**, and returns that provider's `value`. If it reaches the top of the tree without finding one, it returns the `defaultValue` stored on the context object. That is the only situation in which the default is used. Consequences worth stating plainly: - **No provider anywhere:** consumer gets the default, renders, no warning. - **Provider present with `value={undefined}`:** consumer gets `undefined`, *not* the default. React does not treat nullish provider values as "absent"; the provider matched, so its value wins. - **Provider present but below the consumer:** it does not count. Lookup only goes up. ```jsx const ThemeContext = createContext('light'); function Label() { return <span>{String(useContext(ThemeContext))}</span>; } // renders "light" (default) -> <Label /> // renders "undefined" -> <ThemeContext value={undefined}><Label /></ThemeContext> ``` ## The default is not initial state The most common misreading is that `createContext(x)` sets the starting value and the provider takes over afterwards, the way a `useState` initializer does. It does not. There is no "before the provider mounts" window for a component that is rendered inside the provider — a child renders only after its parent renders, so by the time the child's `useContext` runs, the provider is already part of the tree being rendered. A component either has a matching provider above it or it does not, and that answer is decided per render by tree position, not by timing. The practical test: if your default is a plausible-looking object such as `{ user: null, login: () => {} }`, a component accidentally mounted outside the provider will render a signed-out UI and its buttons will do nothing. Nothing in the console tells you why. If your default is `null`, the same mistake throws on the first property access, with a stack trace pointing at the component that is in the wrong place. ## Why React does not throw The default exists so a component that consumes context can also be used standalone — in isolation, in a documentation page, in a snapshot — without a provider wrapper. That is a real convenience, and it is why React deliberately treats "no provider" as a legal, silent case rather than an error. The cost is that a genuinely misplaced component fails quietly, and it is on you to choose whether the default is a usable fallback (`'light'`, an empty array) or a tripwire (`null`, `undefined`). How a codebase enforces the tripwire — where the guard lives, what it says — is a provider-design decision, not something `useContext` does for you. ## Typing note In TypeScript the default drives the inferred type. `createContext(null)` infers `null`, so the usual shape is an explicit type argument that admits the sentinel: ```tsx const AuthContext = createContext<Session | null>(null); ``` That makes every read a nullable value at the type level, which is exactly the honest description of what `useContext` returns when the provider might be missing. ## What to say in an interview Name the three facts in order: the default applies only when no matching provider is found above the component; React returns it silently rather than erroring; and a provider that supplies `undefined` overrides the default rather than falling back to it. Then add the judgment: choose the default so that the missing-provider case is either genuinely fine or immediately loud.

  • If a provider is mounted but passes value={undefined}, why doesn't React fall back to the createContext default?
    Because the fallback is keyed on *finding no provider*, not on the value being nullish. The lookup succeeded — a matching provider was found — so React returns whatever that provider supplied, including `undefined`. Treating nullish as absent would make it impossible to deliberately provide `undefined`, and would hide the difference between "nobody provided this" and "this is intentionally empty".
  • What is a good default to pass when the context has no meaningful standalone value?
    `null`, with the type widened to include it. It makes the missing-provider case fail on first use instead of rendering a plausible-but-wrong UI, and it forces every read site to acknowledge that the value may be absent. Pass a real usable default only when a component genuinely should work without a provider — a theme name, an empty list, a no-op config.
  • Does createContext's default value ever re-evaluate, for example if the module-level expression depends on something that changes?
    No. The argument is evaluated once when the module runs and stored on the context object. It is a plain captured value, not a factory and not a subscription. Anything dynamic has to come from a provider's `value`.

saying these in an interview costs you the question

  • Says useContext throws when no provider is found
  • Calls the default value the context's initial state
  • Thinks value={undefined} falls back to the default
  • Believes the provider replaces the default after mounting
  • Assumes React warns in development about a missing provider

context

open as a page

A React component both calls useContext(ThemeContext) and renders <ThemeContext value="dark"> around its own children. Which value does that component's own useContext call receive, and how does React resolve the lookup?

level: middleimportance: must knowfreq 58%

basics

~20 s

The component reads from the nearest provider above itself, or the context default if there is none. A provider it renders sits below it in the tree, so it affects the children only, never the component that renders it.

open as a page

In React 19, what is the difference between rendering <ThemeContext value={theme}> and <ThemeContext.Provider value={theme}>, and which form is current?

level: juniorimportance: should knowfreq 42%

basics

~20 s

They behave identically. React 19 lets the context object itself be rendered as a provider, so <ThemeContext value={theme}> is the current form. <ThemeContext.Provider> still works and remains valid, with deprecation announced for a future release.

open as a page

A React app renders <AuthContext value={session}> near the root, yet a deep child's useContext(AuthContext) keeps returning the createContext default. The provider is definitely mounted above that child. What identity problem should you suspect, and how would you confirm it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Suspect two different context objects. useContext matches providers by the identity of the object returned by createContext, so a duplicated module or a duplicated React copy gives the provider and the consumer separate objects, and the consumer's upward walk finds no match.

open as a page