Context

useContext

Reads and subscribes to a context value. Re-renders the component whenever the nearest provider's value changes.

Signature

TypeScript
const value = useContext<T>(context: React.Context<T>)

Parameters

ParameterTypeDescription
contextReact.Context<T>The context object created by React.createContext. The hook returns the value from the nearest matching Provider above in the tree.

Return Value

The current context value, determined by the value prop of the nearest Provider for this context above the calling component.

Examples

Theme Context
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext<'light' | 'dark'>('light');

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  return (
    <ThemeContext.Provider value={theme}>
      {children}
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Toggle
      </button>
    </ThemeContext.Provider>
  );
}

function Card() {
  const theme = useContext(ThemeContext);
  return <div className={theme === 'dark' ? 'bg-black' : 'bg-white'}>Card</div>;
}
Auth Context
import { createContext, useContext } from 'react';

interface AuthContextType {
  user: { name: string; email: string } | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
}

function Dashboard() {
  const { user, logout } = useAuth();
  return (
    <div>
      <p>Welcome, {user?.name}</p>
      <button onClick={logout}>Log out</button>
    </div>
  );
}
Nested Contexts
import { createContext, useContext } from 'react';

const LangContext = createContext('en');
const CurrencyContext = createContext('USD');

function PriceTag({ amount }: { amount: number }) {
  const lang = useContext(LangContext);
  const currency = useContext(CurrencyContext);

  const formatted = new Intl.NumberFormat(lang, {
    style: 'currency',
    currency,
  }).format(amount);

  return <span>{formatted}</span>;
}

// Wrap once at the top:
// <LangContext.Provider value="de">
//   <CurrencyContext.Provider value="EUR">
//     <PriceTag amount={42.5} />  → "42,50 €"
//   </CurrencyContext.Provider>
// </LangContext.Provider>

Common Pitfalls

!

Using useContext without a Provider above in the tree — the hook returns the default value from createContext, which may be unexpected.

!

Placing frequently-changing values in context causes all consumers to re-render. Split contexts or memoize the value object.

!

Passing a new object literal as the Provider value on every render, causing unnecessary re-renders of all consumers.

!

Not creating a custom hook wrapper that throws when context is null, leading to silent bugs.

Understanding useContext

useContext provides a way to pass data through the component tree without manually threading props at every level. It is React's built-in solution to the "prop drilling" problem, where intermediate components must forward props they don't use just to pass them deeper. Combined with createContext and a Provider component, useContext forms a clean dependency injection system.

When the value of a Provider changes, every component calling useContext for that context will re-render. This behavior is by design but can become a performance concern if the context value changes frequently or if many components subscribe to it. A common optimization is to split a large context into smaller, focused contexts — one for user data, another for theme, another for locale — so that changes only affect relevant consumers.

Another important optimization is memoizing the context value. If you pass an object literal as the value prop to a Provider, a new object is created on every render, causing all consumers to re-render even when the actual data hasn't changed. Wrapping the value in useMemo solves this. For contexts that combine state with dispatch (like a reducer pattern), consider providing state and dispatch through separate contexts.

A best practice is to create a custom hook that wraps useContext and includes a null check. This provides a better developer experience by throwing a descriptive error when the hook is called outside its Provider, rather than silently returning undefined. Libraries like Zustand and Jotai offer alternative global state approaches that avoid some of context's re-rendering trade-offs.

Related Hooks

Explore All React Hooks

Browse our complete reference of 19 React hooks with signatures, examples, pitfalls, and in-depth explanations.