React useContext Hook
Table of Contents + −
In the last lesson you learned about the React useCallback Hook, so your functions stay stable across renders. This lesson meets useContext, the hook that lets any component read shared data directly, no matter how deep it sits, with no prop drilling through the middle.
🤔 Why do we need useContext?
Here’s the pain. Some data is needed in many places, like the logged-in user, the theme, or the language.
- A value like the current theme is needed by buttons, headers, and cards all over the tree.
- To get it from the top down to a deep button, you thread it through every component in between.
- Those middle components don’t use the theme at all, they just forward it.
- Add one more value to share and you repeat all that drilling again.
So useContext is about reaching shared data directly. You put the value in a Context, and any component inside can pull it out, skipping every level in between.
This is the quick tour
This lesson is the hook-level intro to context, so you recognize useContext among the hooks. There’s a whole later module that covers creating context, the provider, and best practices in depth, so we keep this one short and focused on reading a value.
🧩 The three pieces of context
Context always has the same three parts, and once you see them together the whole thing clicks.
- Create the context with
createContext. This makes the shared “box”. - Wrap part of your tree in the context’s
Providerand give it avalue. Everything inside can now read that value. - Read the value anywhere inside with
useContext(TheContext).
So the flow is: create, provide at the top, read at the bottom. The middle components do nothing.
💡 A simple example
Let’s share a theme without drilling. We create a context, provide a value at the top, and read it in a deep child.
import { createContext, useContext } from "react";
// 1. create the context with a default valueconst ThemeContext = createContext("light");
function App() { // 2. provide a value to everything inside return ( <ThemeContext.Provider value="dark"> <Page /> </ThemeContext.Provider> );}
function Page() { // Page does NOT need the theme - it just renders a deep child return <Toolbar />;}
function Toolbar() { // 3. read the value directly, no props passed in const theme = useContext(ThemeContext); return <button className={theme}>Current theme: {theme}</button>;}Follow the value from top to bottom.
createContext("light")makes the box, with"light"as the fallback if no provider is found.ThemeContext.Provider value="dark"wraps the tree and sets the shared value to"dark".Pageis in the middle and gets no theme prop. It just rendersToolbar.ToolbarcallsuseContext(ThemeContext)and gets"dark"straight from the provider.
Output
A button reading: Current theme: dark🔍 What useContext actually does
The hook itself is the easy part, so let’s see its one job.
- You call
useContext(SomeContext)and pass the context object you created. - It returns the current
valuefrom the nearest matchingProviderabove this component. - When that provider’s value changes, every component reading it re-renders with the new value.
- If there’s no provider above, it returns the default you gave to
createContext.
So useContext is just the “read” step. The provider decides the value, and the hook hands it to whoever asks.
🌍 Where context is a good fit
Context shines for data that many components need but that changes rarely, so use it for the truly shared, app-wide things.
- The current theme, like light or dark mode.
- The logged-in user and whether they’re signed in.
- The chosen language for an app that supports many.
- App-wide settings that lots of screens read.
So if a value is needed all over and would otherwise be drilled through many layers, that’s a strong sign context fits.
⚠️ Common Mistakes
The most common slip is reading a context with no provider above it, so you silently get the default.
// ❌ no <ThemeContext.Provider> anywhere above, so this is always the defaultconst theme = useContext(ThemeContext); // "light", even if you expected "dark"
// ✅ make sure a Provider wraps the component that reads it<ThemeContext.Provider value="dark"> <Toolbar /> {/* now useContext here gets "dark" */}</ThemeContext.Provider>Watch out for these.
- Don’t forget the
Provider. Without one above,useContextreturns the default, which feels like a bug that isn’t. - Don’t put everything in one giant context. When its value changes, every reader re-renders. Split unrelated data into separate contexts.
- Don’t use context for fast-changing local state. A value that changes many times a second is better as local state, not shared context.
✅ Best Practices
A few habits keep context tidy and predictable.
- Use context for app-wide data that many components read, like theme, user, or language.
- Keep separate concerns in separate contexts, so a change in one doesn’t re-render readers of the other.
- Always wrap the components that read a context in its
Provider. - For data needed by only a couple of nearby components, plain props are simpler than context.
Not a replacement for all props
Context is for shared, broadly-used data, not for every prop. If only one or two close components need a value, passing a prop is clearer. Use context to kill real prop drilling, not to avoid props entirely.
🧩 What You’ve Learned
- ✅
useContextlets any component read shared data directly, skipping every level in between - ✅ Context has three pieces:
createContext, aProviderwith avalue, anduseContextto read it - ✅
useContext(SomeContext)returns the value from the nearestProviderabove the component - ✅ When the provider’s value changes, every reader re-renders with the new value
- ✅ With no provider above, you get the default passed to
createContext - ✅ It’s a great fit for theme, user, and language, but not for fast-changing local state
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What problem does useContext mainly solve?
Why: useContext lets a deep component read shared data directly from a Provider, so you don't have to thread a prop through every level in between.
- 2
What are the three pieces of context?
Why: You create the context with createContext, provide a value with the Provider, and read that value anywhere inside with useContext.
- 3
What does useContext return?
Why: useContext returns the current value from the closest Provider above. Only if there is no Provider does it fall back to the default given to createContext.
- 4
When is context NOT a good fit?
Why: Context re-renders every reader when its value changes. Fast-changing values are better kept as local state. Context suits shared data that changes rarely.
🚀 What’s Next?
Now you can share data across your whole app without drilling props through every layer. Next you’ll learn how to bundle your own hook logic into a reusable function, so you can package and share behavior just like React’s built-in hooks.