skip to content

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%

answer

  1. reference equality, not the name
  2. default means no match for this object
  3. same source, two module instances
  4. compare with === from both sides
  5. dedupe the module, not the component

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.

solid answer

~50 s

`useContext` matches on **object identity**, not on name: it walks up looking for a provider created from the exact object you passed it. If the provider file and the consumer file end up importing two different `createContext` results, the lookup legitimately finds nothing and returns the default. The usual causes are a module that got bundled twice — a duplicated package instance in the dependency tree, a package shipping both ESM and CJS builds that both get pulled in, or a monorepo package resolved from two locations — and, less often, two copies of React itself. To confirm, import the context object into one module from both paths and compare with `===`, or log the object from the provider side and the consumer side and check they are the same reference; React DevTools showing a provider that no consumer is attached to points the same way. The fix is deduplication so one module instance exists, not a workaround in the component.

code

javascript · 6 lines
javascript
import { AuthContext as fromApp } from './context/auth.js';
import { AuthContext as fromLib } from 'some-lib/auth.js';

// same source, two module instances => two distinct keys
console.log(fromApp === fromLib);
console.log(fromApp, fromLib);

go deeper

for a junior

Know that useContext matches the exact object returned by createContext, and that a context object belongs at module scope so it is created once.

for a middle

Explain that reading the default means no provider for that specific object was found above, and be able to check it by comparing the two imported context objects with ===.

for a senior

Show the diagnostic sequence — rule out tree shape first, then prove or disprove object identity — and name real duplication causes such as a package installed twice, dual ESM/CJS builds, or a library bundling its own React.

for a principal

Own prevention: single-instance guarantees for shared modules, React kept as a peer dependency in internal libraries, and dependency-tree checks that catch duplicate instances before they reach an application.

## Why identity is the thing to check `createContext` returns an object, and that object *is* the key. When a component calls `useContext(AuthContext)`, React walks up its ancestors and compares each provider's context object against the one it was handed. The comparison is reference equality. Nothing about the variable name, the display name, or the shape of the value participates. So "a provider is mounted above me but I read the default" has one honest meaning: the provider above is for a *different object* than the one the consumer asked about. Once you accept that, the search narrows to how two objects came to exist. ```js // module evaluated twice => two distinct objects export const AuthContext = createContext(null); ``` ## How a module ends up evaluated twice - **Duplicated package instance.** The provider and the consumer resolve the same package from two different locations in the dependency tree, so its module graph is instantiated twice and every module-level `createContext` runs twice. - **Dual-format shipping.** A package publishes both an ESM and a CommonJS build; one importer gets one, another gets the other, and each build has its own module instance. - **Monorepo resolution.** A workspace package is consumed both through its published entry point and through a direct deep path, producing two distinct modules with the same source. - **Two copies of React.** Rarer but worse: a library bundles its own React rather than treating it as a peer, so its hooks run against a different renderer. Context breakage is one symptom among several — you usually also see invalid-hook-call errors. - **Accidental re-creation.** A `createContext` call sitting inside a component or a factory rather than at module scope makes a fresh context object on every call. Any consumer holding a reference to a different call's result never matches. That last one is worth checking first because it needs no bundler knowledge to spot: `createContext` belongs at module scope, evaluated once. ## Confirming it, cheaply The direct test is a reference comparison. Import the context object from both suspect paths into a single scratch module and compare: ```js import { AuthContext as fromApp } from './context/auth'; import { AuthContext as fromLib } from 'some-lib/auth'; console.log(fromApp === fromLib); // false => two objects, mystery solved ``` If you cannot import both, log the object itself from the provider component and from the consuming component and compare the two references in the console — identical source with different identity is the signature. React DevTools helps from the other direction: inspect the consumer and look at the context it is reading, and inspect the provider; a provider that no consumer is bound to, sitting right above consumers that show default values, is the same finding by a different route. A useful sanity check before any of this: confirm the provider really is an ancestor in the **React** tree, not merely nearby in the DOM or rendered as a sibling. Ruling that out first stops you chasing a bundling ghost when the tree shape is simply wrong. ## Fixing it The fix belongs at the module level, not in the component. Ensure a single instance of the module that calls `createContext`: deduplicate the offending dependency so one copy is installed, resolve the package to a single build, or make the context module the single owned source that both sides import from the same path. For a library, publishing React as a peer dependency and not bundling it is the standing rule. What you must **not** do is paper over it — passing the value as a prop past the boundary, or creating a second provider for the second context object, leaves two sources of truth that will diverge. ## Interview framing The grader is listening for the leap from a symptom to the right model: context is matched by object identity, therefore the default means "no provider for *this object*", therefore the question is which object each side is holding. Naming a plausible duplication mechanism and a concrete `===` check is what separates a candidate who has debugged this from one who has only read about context.

  • How would you distinguish this from the simpler case where the provider just isn't a React ancestor of the consumer?
    Check tree shape first in React DevTools: select the consumer and confirm the provider appears among its ancestors, not merely nearby in the DOM or as a sibling. If it genuinely is an ancestor and the consumer still reads the default, identity is the remaining explanation and the `===` comparison settles it.
  • Why does a library bundling its own copy of React cause this, and what is the correct packaging?
    A second React copy means a second renderer with its own internals, so the library's components resolve context and hooks against a tree your app's React never sees. The correct packaging is React as a peer dependency, left external in the bundle, so the host application supplies the single copy that everything shares.
  • What is wrong with working around this by passing the context value down as a prop across the boundary?
    It leaves both context objects alive with different values, so any code still reading the other one silently gets the default — you have hidden the bug rather than fixed it, and created a second source of truth that will drift. The duplication is a dependency-resolution defect and should be fixed there.

saying these in an interview costs you the question

  • Assumes contexts match by name or display name
  • Blames provider value identity or missing memoization
  • Calls createContext inside a component or factory
  • Adds a second provider instead of deduplicating the module
  • Believes React warns when a consumer finds no matching provider

context