Context for Dependency Injection
Context can make services, configuration, clients, and other dependencies available to components without passing them through every intermediate prop. It is useful for dependencies that are shared across a component subtree and whose consumers are known through the component hierarchy.
In React, Context provides a way to expose a value from a provider to descendant components without explicitly threading that value through each component's props. A common dependency-injection use case is a service such as an API client, feature-flag configuration, localization service, or authentication-related client.
The provider owns the dependency and supplies it through a context object. A descendant reads the dependency with `useContext` (or another context consumer API). This separates a component from the code that constructs the dependency, which can make components easier to configure and test.
Context does not automatically make a dependency globally appropriate. Its scope is the provider's subtree, and consumers rerender when the context value they observe changes. For frequently changing values, especially large objects, careless provider design can cause unnecessary rendering work.
A useful pattern is to define the context and provider around a domain-specific dependency rather than creating a single catch-all context. The provider can construct or receive the service, while consumers depend only on the interface they need.
For testing, a component can be rendered under a test provider that supplies a fake or mock implementation. This keeps construction details outside the consumer and avoids passing test doubles through unrelated component layers.
Key points
- Context can inject dependencies through a component subtree without prop drilling.
- A provider supplies the dependency; consumers retrieve it with `useContext`.
- Context is scoped by the provider hierarchy rather than being inherently global.
- Changing a context value can cause subscribed consumers to rerender.
- Domain-specific contexts and stable provider values generally make dependency boundaries clearer.
Interview questions
What problem does React Context solve when used for dependency injection?
Context avoids passing the same dependency through intermediate components that do not use it. A provider makes the dependency available to descendants, and consumers retrieve it with `useContext`. This is useful for services or configuration that many components in one subtree need. The dependency remains scoped to the provider hierarchy.
How would you use Context to inject an API client?
I would define a context whose type represents the API client's interface, then provide a concrete implementation near the application boundary. Components would call `useContext` to obtain that interface rather than constructing the client themselves. In tests, the provider can supply a fake implementation. I would keep the context domain-specific so unrelated services do not become coupled to the same provider.
What happens when a Context value changes?
Components that consume that context are notified when the provided value changes according to React's context comparison behavior. This means provider design can affect rendering performance. Passing a newly created object as the context value on every provider render can cause consumers to update even when the underlying dependency has not meaningfully changed. Stable values and appropriately scoped contexts can reduce unnecessary work.
Why might Context be preferable to a module-level singleton for dependency injection?
Context makes dependency scope explicit in the React tree and allows different subtrees to receive different implementations. That is valuable for tests, multi-tenant UI, previews, or other situations where the implementation needs to vary by subtree. A module singleton does not naturally provide that scoped override mechanism. Context also keeps dependency resolution connected to component composition rather than hidden in module state.
What would you consider when deciding whether to put a value in Context?
I would first ask whether the value is needed by multiple descendants and whether passing it explicitly would create meaningful prop plumbing. I would also consider how often it changes, because frequently changing context can affect many consumers. The dependency's scope matters as well: a value needed by only one or two nearby components may be clearer as props. Finally, I would consider whether a dedicated context gives a clean ownership and testing boundary.