React Context API

Context lets you share data across a component tree without passing it through every level as props. It is the standard React solution for things like the current user, a theme, or a language setting.

Before reaching for a state management library, check whether Context solves your problem. For many apps, it does.

The prop drilling problem

Suppose your app has a user object at the top level. Several components deep in the tree need to display the user’s name. Without Context, you pass user down through every component in between, even the ones that do not use it directly.

function App() {
  const user = { name: 'Anna', role: 'admin' };
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  return <UserMenu user={user} />;
}

function UserMenu({ user }) {
  return <p>Logged in as: {user.name}</p>;
}

Layout and Sidebar receive user only to pass it along. They do not use it themselves. This is prop drilling: the data has to travel through every intermediate component to reach the one that needs it.

Context removes those intermediate hand-offs.

Creating a context

Call createContext with a default value. The default is used when a component reads the context but has no Provider above it in the tree.

// ThemeContext.js
import { createContext } from 'react';

export const ThemeContext = createContext('light');

Export the context so any component in the app can import it.

Providing a context value

Wrap the part of the tree that needs access to the value in a Context.Provider. Any component inside the Provider can read the context value, no matter how deeply nested.

import { ThemeContext } from './ThemeContext';

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Layout />
    </ThemeContext.Provider>
  );
}

To make the value dynamic, store it in state and pass the state variable as the value prop:

import { useState } from 'react';
import { ThemeContext } from './ThemeContext';

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <Layout />
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle theme
      </button>
    </ThemeContext.Provider>
  );
}

Reading a context value with useContext

Any component inside the Provider can call useContext to read the value. No props needed.

import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemedCard() {
  const theme = useContext(ThemeContext);

  const style = {
    background: theme === 'dark' ? '#333' : '#fff',
    color: theme === 'dark' ? '#fff' : '#333',
    padding: '16px',
    borderRadius: '8px',
  };

  return <div style={style}>Current theme: {theme}</div>;
}

ThemedCard does not receive any props. It reads the theme directly from Context and re-renders whenever the Provider’s value changes.

A complete working example

Here is a full theme toggle built with Context. Four components, no prop drilling.

// ThemeContext.js
import { createContext } from 'react';
export const ThemeContext = createContext('light');
// App.jsx
import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import ThemeToggle from './ThemeToggle';
import ThemedCard from './ThemedCard';

function App() {
  const [isDark, setIsDark] = useState(false);
  const theme = isDark ? 'dark' : 'light';

  return (
    <ThemeContext.Provider value={{ theme, setIsDark }}>
      <div style={{ padding: '24px' }}>
        <ThemeToggle />
        <ThemedCard title="Hello" body="This card reads the theme from Context." />
        <ThemedCard title="Another card" body="Same Context, different component." />
      </div>
    </ThemeContext.Provider>
  );
}

export default App;
// ThemeToggle.jsx
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemeToggle() {
  const { theme, setIsDark } = useContext(ThemeContext);

  return (
    <button onClick={() => setIsDark((prev) => !prev)}>
      Switch to {theme === 'light' ? 'dark' : 'light'} mode
    </button>
  );
}

export default ThemeToggle;
// ThemedCard.jsx
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemedCard({ title, body }) {
  const { theme } = useContext(ThemeContext);

  const style = {
    background: theme === 'dark' ? '#1e1e1e' : '#f5f5f5',
    color: theme === 'dark' ? '#f0f0f0' : '#111',
    padding: '16px',
    borderRadius: '8px',
    marginTop: '12px',
  };

  return (
    <div style={style}>
      <h3>{title}</h3>
      <p>{body}</p>
    </div>
  );
}

export default ThemedCard;

The Provider passes an object with both the current theme and the setter. Both ThemeToggle and ThemedCard can read from it with useContext, and neither needs props from App.

What Context is good for

Context works well for data that many components across the tree need to read:

  • Current logged-in user and authentication state
  • App-wide theme (light/dark mode)
  • Language and locale settings
  • Global notification or toast state
  • Feature flags

What Context is not good for

Context is not a drop-in replacement for all state management.

Do not use it for data that only one or two components need. If only a parent and a direct child share a value, props are simpler and more explicit.

Do not use it for values that change frequently. Every component that calls useContext re-renders when the Provider’s value changes. If the value updates many times per second (like a cursor position or scroll offset), Context will cause performance problems. Use a more targeted approach for those cases.

Do not use it for server data. If you are fetching users, posts, or products from an API, Context does not handle caching, background refetching, or stale data. Use TanStack Query for that.

Context vs useState vs a library

SituationWhat to use
Data used only by one componentuseState inside that component
Data shared between a parent and one or two childrenLift state up, pass via props
Data needed across many components in a subtreeContext
Server data (API responses, caching, sync)TanStack Query
Complex global client state across many featuresZustand or Redux Toolkit

Start with the simplest option and reach for Context only when props become genuinely unwieldy.

Common mistakes

Forgetting the Provider.

If a component calls useContext but has no Provider above it in the tree, it gets the default value passed to createContext. This is often undefined or an empty string, which causes silent bugs. Always check that your Provider wraps the component that needs the context.

One giant context for everything.

If you put unrelated values into one context (theme, user, cart, language), every component that reads any one of those values will re-render whenever any of them changes. Split unrelated values into separate contexts.

Using Context for high-frequency updates.

Animating a value, tracking mouse position, or responding to scroll events at 60fps through Context will cause performance problems. Use a ref or a dedicated library for those cases.

FAQ

Does the default value in createContext matter?

Only when a component reads the context without a Provider anywhere above it in the tree. In practice, you almost always have a Provider at the top of your app, so the default is rarely used. It can be useful for testing individual components in isolation.

Can I have multiple Providers for the same context?

Yes. A Provider lower in the tree will override the one above it for everything nested inside it. This is useful for section-specific themes or temporary overrides.

Should I always export a custom hook to wrap useContext?

It is a good pattern for larger codebases: export a useTheme() function that calls useContext(ThemeContext) internally. This lets you add error checking (throwing if used outside the Provider) and keeps the context import out of every consumer file. For small projects, calling useContext directly is fine.

  • Lifting State Up : the step before Context, and what leads you to need it
  • useState : the hook that drives most Context values
  • useReducer : a good pairing with Context for more complex state logic