How to Use useContext in React
What you’ll build or solve
You’ll share data across multiple components without drilling props through every level.
When this approach works best
useContext works best when you need to:
Learn React on Mimo
- Share app-wide settings like theme, language, or feature flags across many screens.
- Store session data like the current user or auth state for lots of components.
- Coordinate UI state across siblings, like a sidebar open state used by the header and layout.
Skip useContext when only a parent and one child need the data. Props stay simpler. Also, avoid putting fast-changing state, like a text input value, into context for large trees, since each update can re-render many consumers.
Prerequisites
- A React app set up
- Comfort with React components and
useState
Step-by-step instructions
1) Create a context object
Create a context in its own file so you can import it anywhere.
// src/contexts/ThemeContext.js
import {createContext }from"react";
exportconstThemeContext=createContext("light");
createContext returns an object you will use in two places: the Provider and useContext. The "light" value is the default when no provider exists above the component.
2) Provide a value with Provider
Wrap the part of your app that should have access to the value.
// src/App.jsx
import {useState }from"react";
import {ThemeContext }from"./contexts/ThemeContext";
importPagefrom"./Page";
exportdefaultfunctionApp() {
const [theme,setTheme]=useState("light");
return (
<ThemeContext.Providervalue={theme}>
<Page
onToggle={() =>
setTheme((t) => (t==="light"?"dark":"light"))
}
/>
</ThemeContext.Provider>
);
}
What to look for:
- Any component that needs the context must render under
<ThemeContext.Provider>. - If it renders outside, it will receive the default value from
createContext(...).
3) Read the value with useContext
Call useContext inside your component to read the nearest provider’s value.
JavaScript
// src/Header.jsx
import {useContext }from"react";
import {ThemeContext }from"./contexts/ThemeContext";
exportdefaultfunctionHeader() {
consttheme=useContext(ThemeContext);
return (
<headerclassName={theme==="dark"?"dark":"light"}>
Header
</header>
);
}
What to look for:
- Call
useContextat the top level of your component during render. - Don’t call it inside event handlers, loops, or conditionals.
Examples you can copy
Example 1: Share a theme value
// ThemeContext.js
import {createContext }from"react";
exportconstThemeContext=createContext("light");
JavaScript
// App.jsx
import {useState }from"react";
import {ThemeContext }from"./ThemeContext";
importHeaderfrom"./Header";
exportdefaultfunctionApp() {
const [theme,setTheme]=useState("light");
return (
<ThemeContext.Providervalue={theme}>
<Header/>
<button
onClick={() =>
setTheme((t) => (t==="light"?"dark":"light"))
}
>
Toggle
</button>
</ThemeContext.Provider>
);
}
JavaScript
// Header.jsx
import {useContext }from"react";
import {ThemeContext }from"./ThemeContext";
exportdefaultfunctionHeader() {
consttheme=useContext(ThemeContext);
return<h1>Theme: {theme}</h1>;
}
Example 2: Pass multiple values through one provider
JavaScript
// AuthContext.js
import {createContext }from"react";
exportconstAuthContext=createContext(null);
JavaScript
// App.jsx
import {useState }from"react";
import {AuthContext }from"./AuthContext";
importAccountMenufrom"./AccountMenu";
exportdefaultfunctionApp() {
const [user,setUser]=useState({ id:1, name:"Sam" });
constauth= {
user,
signOut() {
setUser(null);
},
};
return (
<AuthContext.Providervalue={auth}>
<AccountMenu/>
</AuthContext.Provider>
);
}
JavaScript
// AccountMenu.jsx
import {useContext }from"react";
import {AuthContext }from"./AuthContext";
exportdefaultfunctionAccountMenu() {
constauth=useContext(AuthContext);
if (!auth?.user)return<ahref="/login">Sign in</a>;
return (
<div>
<span>{auth.user.name}</span>
<buttononClick={auth.signOut}>Sign out</button>
</div>
);
}
Example 3: Feature flags for conditional UI
// FlagsContext.js
import {createContext }from"react";
exportconstFlagsContext=createContext({ newNav:false });
JavaScript
// App.jsx
import {FlagsContext }from"./FlagsContext";
importNavfrom"./Nav";
exportdefaultfunctionApp() {
constflags= { newNav:true };
return (
<FlagsContext.Providervalue={flags}>
<Nav/>
</FlagsContext.Provider>
);
}
JavaScript
// Nav.jsx
import {useContext }from"react";
import {FlagsContext }from"./FlagsContext";
importNewNavfrom"./NewNav";
importOldNavfrom"./OldNav";
exportdefaultfunctionNav() {
constflags=useContext(FlagsContext);
returnflags.newNav?<NewNav/>:<OldNav/>;
}
Common mistakes and how to fix them
Mistake 1: Using useContext outside the matching Provider
What you might do:
JavaScript
import {useContext }from"react";
import {AuthContext }from"./AuthContext";
exportdefaultfunctionAccountMenu() {
constauth=useContext(AuthContext);
return<p>{auth.user.name}</p>;
}
Why it breaks:
Without a provider above, auth will be the default value, often null, so reading auth.user throws an error.
Fix: Wrap your component tree with the provider.
CSS
<AuthContext.Providervalue={{ user: { name:"Sam" } }}>
<AccountMenu/>
</AuthContext.Provider>
Mistake 2: Context value changes cause extra re-renders
What you might do:
<AuthContext.Providervalue={{ user, signOut }}>
<App/>
</AuthContext.Provider>
Why it breaks:
If you create a new object every render, consumers can re-render more often than you expect.
Fix: Stabilize the value when needed, for example, with useMemo, or split context when performance becomes a real issue.
import {useMemo }from"react";
constvalue=useMemo(
() => ({ user, signOut }),
[user,signOut]
);
<AuthContext.Providervalue={value}>
<App/>
</AuthContext.Provider>;
Troubleshooting
- If you see “Cannot read properties of null/undefined,” check provider placement and the context default. Make sure the provider wraps the component using
useContext. - If you see “Invalid hook call,” confirm you only call
useContextinside a React component or custom hook and that your React versions match. - If a consumer doesn’t update, check that you are updating the provider value. Updating an unrelated state won’t change the context.
- If updates feel slow, check how many components consume the context. Context updates can re-render many consumers. Stabilize the value with
useMemoor split contexts into smaller pieces when needed.
Quick recap
- Create a context with
createContext(defaultValue). - Wrap the tree with
<YourContext.Provider value={...}>. - Read the value with
useContext(YourContext)inside components. - Pass a single value or an object when you need multiple values.
- If performance becomes an issue, stabilize provider values or split contexts.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot