useContext
useContext reads a value from a Context Provider higher in the tree, without prop-drilling. Use for theme, auth user, i18n, anything that’s genuinely app-wide.
Provider + consumer + custom hook
EXAMPLE
import { createContext, useContext, useState, useMemo } from 'react';
// 1) Create context with a sensible default
const ThemeContext = createContext({ theme: 'light', toggle: () => {} });
// 2) Provider wraps the tree
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(
() => ({ theme, toggle: () => setTheme((t) => (t === 'light' ? 'dark' : 'light')) }),
[theme],
);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
// 3) Consumer via useContext
function Button() {
const { theme, toggle } = useContext(ThemeContext);
return <button onClick={toggle}>Theme: {theme}</button>;
}
// 4) App
export default function App() {
return (
<ThemeProvider>
<header><Button /></header>
<main>…</main>
</ThemeProvider>
);
}
// 5) Custom hook — friendlier API + runtime error
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used inside ThemeProvider');
return ctx;
}
// 6) Real-world: auth context
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
api.me().then(setUser).finally(() => setLoading(false));
}, []);
const value = useMemo(
() => ({
user,
loading,
signIn: async (email, pw) => setUser(await api.signIn(email, pw)),
signOut: async () => { await api.signOut(); setUser(null); },
}),
[user, loading],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export const useAuth = () => useContext(AuthContext);
// 7) Multiple contexts — compose
<AuthProvider>
<ThemeProvider>
<I18nProvider>
<App />
</I18nProvider>
</ThemeProvider>
</AuthProvider>
// 8) Performance — every consumer re-renders on value change
// Mitigations:
// • useMemo the value object
// • Split context into smaller contexts (state vs dispatch)
// • Use a state-management library (Zustand, Jotai) for hot paths
const StateContext = createContext();
const DispatchContext = createContext();
function CounterProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initial);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>{children}</DispatchContext.Provider>
</StateContext.Provider>
);
}
// Components that only DISPATCH don't rerender when state changes.
// 9) Selector pattern — read only what you need
// Native context doesn't support selectors; libs do (use-context-selector).
// Or just split context.
// 10) When NOT to use context
// • Server state (TanStack Query / SWR — has cache + revalidation)
// • Frequent updates (Zustand, Jotai, Redux Toolkit)
// • Local-to-one-component state (just useState)
// 11) Common bugs
// • Forgetting <Provider> wrapping → consumers get default value silently
// • value={{ ... }} inline → new object each render → every consumer rerenders
// • Calling context functions during render → infinite loops
// • Storing functions in context without useCallback → reference changes
Why it matters
useContext shines for app-wide values that rarely change (auth, theme, i18n). For frequent updates, split contexts or reach for Zustand/Jotai — React’s context model rerenders every consumer on every change.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
const Theme = createContext('light');
function Toolbar() {
const theme = useContext(Theme);
return <div className={theme} />;
}
Try it Yourself »
Discussion
Loading…