useContext Hook
Learn how the useContext Hook lets any nested component read (and update) shared context values without wrapping every consumer in a render-prop.
Introduction
In the previous lesson you met the Context API and saw how it lets you share a value — like a theme or a logged-in user — across many components without passing props down manually at every level. That lesson used <Context.Consumer> to read the value, which works but adds an extra layer of nested render-prop syntax around every component that needs the data.
The useContext Hook solves this cleanly. It lets any function component read a context value directly, in a single line, with no wrapping component required. This lesson shows how to replace Context.Consumer with useContext, how to update context state from deeply nested children, and how to combine several contexts in the same component tree.
- Why useContext(MyContext) replaces the <Context.Consumer> pattern.
- How to build a ThemeContext that toggles light/dark styling across nested components.
- How to update context state from a deeply nested consumer using an updater function.
- How to read and combine multiple contexts inside one component.
From Context.Consumer to useContext
Context.Consumer requires wrapping your JSX in a component whose only child is a function that receives the context value as an argument. This nesting works, but it gets awkward quickly, especially once you need to read more than one context or combine the value with other JSX. useContext removes that wrapper entirely — you call the Hook at the top of your component, and it returns the current context value straight away.
Context.Consumer (render-prop)
- function Toolbar() {
- return (
- <ThemeContext.Consumer>
- {(theme) => (
- <div className={theme}>
- Toolbar
- </div>
- )}
- </ThemeContext.Consumer>
- );
- }
useContext (Hook)
- function Toolbar() {
- const theme = useContext(ThemeContext);
- return (
- <div className={theme}>
- Toolbar
- </div>
- );
- }
Both versions read the exact same context value. The useContext version is flatter, easier to read, and easier to combine with other Hooks like useState or useEffect in the same component.
If you find yourself reaching for <Context.Consumer>, reach for useContext instead. useContext is the standard way to read context in modern function components — Consumer is mostly seen in older codebases or class components, which cannot use Hooks at all.
A Worked Example: Theme Context
Let's build a small app where a ThemeContext controls light/dark styling across several nested components — a Toolbar and a Button — without either of them receiving the theme as a prop.
import { createContext } from 'react';
// Default value is used only if a component reads the
// context outside of any matching Provider.
export const ThemeContext = createContext('light');import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import Toolbar from './Toolbar';
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<div className={`app ${theme}`}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<Toolbar />
</div>
</ThemeContext.Provider>
);
}
export default App;import ThemedButton from './ThemedButton';
// Toolbar does not use the theme itself, and does not
// need to accept or forward a theme prop at all.
function Toolbar() {
return (
<div className="toolbar">
<ThemedButton label="Save" />
<ThemedButton label="Cancel" />
</div>
);
}
export default Toolbar;import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemedButton({ label }) {
const theme = useContext(ThemeContext);
const style = {
background: theme === 'dark' ? '#1f1f1f' : '#ffffff',
color: theme === 'dark' ? '#ffffff' : '#1f1f1f',
border: '1px solid #888',
};
return <button style={style}>{label}</button>;
}
export default ThemedButton;[Toggle Theme]
Toolbar
[Save] ← styled dark or light
[Cancel] ← styled dark or lightNotice that Toolbar sits in between App and ThemedButton but never touches the theme value at all — it just renders its children. ThemedButton, several layers deep, calls useContext(ThemeContext) and gets the live value directly, updating automatically whenever App's state changes.
Updating Context From a Nested Consumer
So far the theme toggle button lives in App itself. In real apps, the control that changes a shared value is often deep inside the tree — for example, a "Toggle Theme" button that lives inside a settings panel several layers down. To make that possible, put both the value and an updater function into the context, usually as an object.
import { createContext } from 'react';
// The context now carries both the current theme and
// a function to change it.
export const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {},
});import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import Toolbar from './Toolbar';
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={`app ${theme}`}>
<Toolbar />
</div>
</ThemeContext.Provider>
);
}
export default App;import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
// This can live any number of levels deep inside Toolbar,
// a settings panel, a modal, anywhere in the tree.
function ThemeToggleButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
}
export default ThemeToggleButton;[Switch to dark mode]
(click)
[Switch to light mode]The state itself (useState) still lives in App, the single source of truth. The context just carries a reference to the setter function down the tree, so any nested component can call it — without App needing to know that component exists at all.
Combining Multiple Contexts
Real applications often need more than one kind of shared data — a theme, the logged-in user, the active language, and so on. Each of these is usually its own context, and a component can call useContext as many times as it needs, once per context.
import { createContext } from 'react';
export const ThemeContext = createContext('light');
export const UserContext = createContext(null);import { useState } from 'react';
import { ThemeContext, UserContext } from './contexts';
import Profile from './Profile';
function App() {
const [theme] = useState('dark');
const [user] = useState({ name: 'Amol', role: 'Admin' });
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<Profile />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
export default App;import { useContext } from 'react';
import { ThemeContext, UserContext } from './contexts';
function Profile() {
const theme = useContext(ThemeContext);
const user = useContext(UserContext);
return (
<div className={theme}>
<h3>{user.name}</h3>
<p>Role: {user.role}</p>
</div>
);
}
export default Profile;Amol
Role: Admin
(styled using the dark theme)Each Provider wraps around the tree independently, and nesting order does not matter for reading the values — Profile simply calls useContext once per context it needs. Keeping each concern in its own context (rather than one giant "AppContext") keeps components from re-rendering for updates they do not actually care about.
Common Mistakes
- Calling useContext with the wrong context object — it must be the exact object returned by createContext, not a string or a copy.
- Forgetting to wrap the component tree in the matching <MyContext.Provider>, which silently falls back to the default value passed to createContext.
- Putting a brand-new object literal directly in value={{ ... }} without memoizing it, causing every consumer to re-render on every parent render.
- Reaching for context for state that only one or two nearby components need — plain props are simpler and easier to trace.
Best Practices
- Call useContext at the top level of your component, just like any other Hook — never inside conditionals or loops.
- Bundle a value and its updater function together in one context object when nested components need to change the data.
- Split unrelated data into separate contexts (theme, user, language) instead of one large combined context.
- Wrap context values that are objects in useMemo when the Provider re-renders often, to avoid unnecessary consumer re-renders.
- Give each context a sensible default value in createContext so components still behave reasonably if a Provider is missing.
Frequently Asked Questions
Do I still need <Context.Consumer> at all?
Rarely. useContext covers the same use case with simpler syntax in function components. Consumer is still needed inside class components, since Hooks cannot be used there.
Can I call useContext more than once in the same component?
Yes. Each call reads a different context, and you can call useContext as many times as you have contexts to read.
What happens if there is no Provider above a component that calls useContext?
The Hook returns the default value passed as the argument to createContext(defaultValue). No error is thrown, but this is easy to miss if you expected a Provider to be present.
Does useContext cause re-renders?
Yes. Every component that calls useContext(MyContext) re-renders whenever the Provider's value prop changes, even if the component only uses part of that value.
Is context a replacement for a state-management library?
For small to medium apps, often yes. For very large apps with frequent updates across many contexts, a dedicated state-management library may offer better performance and tooling — a topic covered later in this course.
Key Takeaways
- useContext(MyContext) reads a context value directly, replacing the nested <Context.Consumer> render-prop pattern.
- Any component, no matter how deeply nested, can call useContext to read the current value.
- Bundling a value and an updater function together in context lets deeply nested components change shared state.
- A component can call useContext multiple times to combine several independent contexts.
- Context re-renders every consumer when its value changes, so keep contexts focused and values memoized where needed.
Summary
The useContext Hook is the modern, straightforward way to read shared data anywhere in your component tree. It removes the extra nesting that Context.Consumer required and reads naturally alongside other Hooks like useState and useEffect.
In this lesson, you replaced Context.Consumer with useContext, built a ThemeContext that styles multiple nested components, updated context state from a deeply nested consumer, and combined two contexts in a single component. Next, you will look at lifting state up — the technique context itself builds on top of.
- You can read context values with useContext instead of Context.Consumer.
- You built a theme toggle that updates styling across nested components.
- You can update context state from deeply nested consumers.
- You can combine multiple contexts inside one component.