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.
JSX
// src/contexts/ThemeContext.js
import { createContext } from "react";
export const ThemeContext = 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.
JSX
// src/App.jsx
import { useState } from "react";
import { ThemeContext } from "./contexts/ThemeContext";
import Page from "./Page";
export default function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={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.
JSX
// src/Header.jsx
import { useContext } from "react";
import { ThemeContext } from "./contexts/ThemeContext";
export default function Header() {
const theme = useContext(ThemeContext);
return (
<header className={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
JSX
// ThemeContext.js
import { createContext } from "react";
export const ThemeContext = createContext("light");
JSX
// App.jsx
import { useState } from "react";
import { ThemeContext } from "./ThemeContext";
import Header from "./Header";
export default function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={theme}>
<Header />
<button
onClick={() =>
setTheme(t => (t === "light" ? "dark" : "light"))
}
>
Toggle
</button>
</ThemeContext.Provider>
);
}
JSX
// Header.jsx
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export default function Header() {
const theme = useContext(ThemeContext);
return <h1>Theme: {theme}</h1>;
}
Example 2: Pass multiple values through one provider
JSX
// AuthContext.js
import { createContext } from "react";
export const AuthContext = createContext(null);
JSX
// App.jsx
import { useState } from "react";
import { AuthContext } from "./AuthContext";
import AccountMenu from "./AccountMenu";
export default function App() {
const [user, setUser] = useState({ id: 1, name: "Sam" });
const auth = {
user,
signOut() {
setUser(null);
},
};
return (
<AuthContext.Provider value={auth}>
<AccountMenu />
</AuthContext.Provider>
);
}
JSX
// AccountMenu.jsx
import { useContext } from "react";
import { AuthContext } from "./AuthContext";
export default function AccountMenu() {
const auth = useContext(AuthContext);
if (!auth?.user) return <a href="/login">Sign in</a>;
return (
<div>
<span>{auth.user.name}</span>
<button onClick={auth.signOut}>Sign out</button>
</div>
);
}
Example 3: Feature flags for conditional UI
JSX
// FlagsContext.js
import { createContext } from "react";
export const FlagsContext = createContext({ newNav: false });
JSX
// App.jsx
import { FlagsContext } from "./FlagsContext";
import Nav from "./Nav";
export default function App() {
const flags = { newNav: true };
return (
<FlagsContext.Provider value={flags}>
<Nav />
</FlagsContext.Provider>
);
}
JSX
// Nav.jsx
import { useContext } from "react";
import { FlagsContext } from "./FlagsContext";
import NewNav from "./NewNav";
import OldNav from "./OldNav";
export default function Nav() {
const flags = useContext(FlagsContext);
return flags.newNav ? <NewNav /> : <OldNav />;
}
Common mistakes and how to fix them
Mistake 1: Using useContext outside the matching Provider
What you might do:
JSX
import { useContext } from "react";
import { AuthContext } from "./AuthContext";
export default function AccountMenu() {
const auth = 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.
JSX
<AuthContext.Provider value={{ user: { name: "Sam" } }}>
<AccountMenu />
</AuthContext.Provider>
Mistake 2: Context value changes cause extra re-renders
What you might do:
JSX
<AuthContext.Provider value={{ 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.
JSX
import { useMemo } from "react";
const value = useMemo(
() => ({ user, signOut }),
[user, signOut]
);
<AuthContext.Provider value={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