Last updated: Apr 29, 2026
Provider
The Provider Pattern uses React Context to wrap a subtree of components that need access to specific data. Child components opt in to that data via useContext instead of receiving it through props at every level. This avoids prop drilling and keeps intermediate components clean.
How It Works
- Create a context with
createContextand give it a default value (ornull). - Wrap a subtree in a Provider component that holds the shared state and passes it through
context.Provider. - Consume the value in any descendant with
useContext— no matter how deep.
Example
import { createContext, useContext, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
interface ThemeContext {
theme: Theme;
toggle: () => void;
}
const ThemeCtx = createContext<ThemeContext | null>(null);
function useTheme(): ThemeContext {
const ctx = useContext(ThemeCtx);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return (
<ThemeCtx.Provider value={{ theme, toggle }}>{children}</ThemeCtx.Provider>
);
}
function Toolbar() {
const { theme, toggle } = useTheme();
return <button onClick={toggle}>Current: {theme}</button>;
}
function App() {
return (
<ThemeProvider>
<Toolbar />
</ThemeProvider>
);
}
Toolbar reads the theme directly from context. Every component between ThemeProvider and Toolbar stays untouched — no forwarded props needed.
Trade-offs
| Pros | Cons |
|---|---|
| Eliminates prop drilling through intermediate components | Harder to trace where data comes from compared to explicit props |
Clean consumer API — one useContext call | Every consumer re-renders when the context value changes, even if it only uses part of it |
| Provider is swappable (e.g. test vs production) | Forgetting to memoize the value object causes unnecessary re-renders on every provider render |