Context solves prop drilling — passing a value through many layers of components that don't themselves use it — by letting any descendant read a value directly, however deep it's nested.
const ThemeContext = createContext("light");
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />; // doesn't need to know about theme at all
}
function ThemedButton() {
const theme = useContext(ThemeContext); // reads directly from the nearest Provider above it
return <button className={theme}>Click</button>;
}
Every component calling useContext(SomeContext) re-renders whenever that context's value changes — regardless of whether that specific component cares about the part of the value that changed. A single large context object holding many unrelated pieces of state ({ user, theme, notifications, ... }) means a change to any one of them re-renders every consumer of the whole context.
The fix is usually splitting one large context into several smaller, more focused ones (UserContext, ThemeContext) so a component only re-renders when the specific piece of state it actually reads changes — or, for very hot paths, pairing context with useMemo on the value it provides, and/or a dedicated state library (Zustand, Redux, Jotai) designed to let components subscribe to just a slice of state.