React

React Context API👨‍💻

Context lets any component in the tree read a value without passing it through every level as props. It solves prop drilling for data like the current user, theme, or locale — values that many components need but few intermediate components care about. It is not a state manager, and treating it like one is the most common mistake teams make.

Key Takeaways

  • 1Context broadcasts a value to all descendants — any component below the provider can read it with `useContext`, skipping every layer in between
  • 2In React 19, you render `<MyContext value={...}>` directly as a JSX element. The `.Provider` wrapper from earlier versions is no longer needed
  • 3Every component that calls `useContext(SomeContext)` re-renders when the context value changes, regardless of which part of the value it actually uses — there are no built-in selectors
  • 4Always wrap context consumption in a custom hook (e.g. `useAuth()` instead of `useContext(AuthContext)`) to enforce the null check and provide a clear error message
  • 5Split state and dispatch into separate contexts when many components only write (dispatch actions) without reading the full state — this prevents unnecessary re-renders
  • 6Context is ideal for values that change infrequently: theme, auth, locale, feature flags. For high-frequency updates or complex state graphs, use Zustand, Jotai, or TanStack Query

Master react context api

Take the React Intermediate course with hands-on lessons and challenges.

Examples

Theme context — the classic use case (React 19 syntax)

tsx

The three-step pattern: create the context, wrap a subtree with the provider, consume via a custom hook. The value is memoized so consumers only re-render when the theme actually changes, not when the provider's parent re-renders for unrelated reasons.

Auth context with async login

tsx

Auth state changes rarely (login, logout) and is needed across the entire app — a textbook Context use case. The login and logout callbacks are stable references via useCallback, so the memoized value only changes when the user object changes.

React 19 vs React 18 provider syntax

tsx

React 19 simplified the provider API. You no longer need .Provider — just render the context itself as a JSX element with a value prop. The old syntax still works but is considered legacy. If you are starting a new project on React 19, use the shorter form.

Split context — state vs dispatch for performance

tsx

A toast notification system is a good split context example. Buttons that trigger toasts only need dispatch — they should not re-render when the toast list changes. The toast display component reads state. Splitting avoids wasted renders in every component that calls useToastDispatch.

Feature flags context — a real production pattern

tsx

Feature flags are fetched once and rarely change during a session, making them ideal for Context. The useFeatureFlag hook provides a clean boolean API. Components conditionally render features without knowing how flags are loaded or stored.

Composing multiple providers — the AppProviders pattern

tsx

Real applications have multiple contexts. Wrapping them in an AppProviders component keeps the app root readable and makes provider order explicit. Provider order matters when inner providers depend on values from outer ones.

Common Mistakes

Mistake:

Putting everything in a single context — user data, theme, cart, and notifications in one big object, causing every consumer to re-render on any change

Fix:

Create one context per domain (AuthContext, ThemeContext, CartContext, etc.). Each context triggers re-renders only in its own consumers. This follows the single-responsibility principle and is the most impactful performance optimization for Context.

Mistake:

Creating a new value object on every render — writing `<MyContext value={{ user, theme }}>` directly in JSX without memoization

Fix:

Wrap the value in `useMemo`: `const value = useMemo(() => ({ user, theme }), [user, theme])`. Without this, every parent re-render creates a new object reference, which forces all consumers to re-render even if the data has not changed.

Mistake:

Using Context for frequently updating data like form input values, scroll position, or animation frames — this re-renders the entire consumer tree on every change

Fix:

Keep high-frequency state local with `useState` in the component that owns it. If multiple components need fast-changing shared state, use Zustand (selector-based subscriptions) or Jotai (atomic updates) instead of Context.

Mistake:

Calling `useContext(SomeContext)` directly without checking for null — when the default value is `null`, components outside the provider silently get `null` and crash later with a confusing error

Fix:

Always create a custom hook that checks for null and throws a descriptive error: `if (!ctx) throw new Error('useAuth must be used within AuthProvider')`. This catches misconfiguration immediately during development.

Best Practices

  • Always expose a custom hook (`useAuth`, `useTheme`) instead of exporting the raw context object — this encapsulates the null check, provides better error messages, and gives you a single place to add logging or derived values later
  • Memoize the context value with `useMemo` and callbacks with `useCallback` — if the provider's parent re-renders, a non-memoized value creates a new object reference that cascades re-renders to every consumer
  • Split state and dispatch into separate contexts when you have components that only trigger actions (buttons, forms) without reading the full state — this is the highest-impact optimization after splitting contexts by domain
  • Reach for Context only after considering simpler alternatives: passing components as props (the "slots" pattern) solves many prop drilling cases without any re-render cost, and lifting state up one level is often enough
  • Do not treat Context as a replacement for a state management library — Context has no selectors, no middleware, no devtools, and no fine-grained subscriptions. Use it for theme, auth, locale, and feature flags. Use Zustand, Jotai, or TanStack Query for everything else

Summary

React Context solves prop drilling by broadcasting a value to all descendants in the tree. In React 19, you render the context directly as a JSX element without the .Provider wrapper. The three-step pattern — create with createContext, provide with a value prop, consume via a custom hook — covers every use case. Context re-renders all consumers on any value change, so keep context values focused (one per domain), memoize them, and split state from dispatch when needed. For data that changes often or requires fine-grained subscriptions, use a dedicated state library instead.

Practice React with hands-on challenges

Learn react context api hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master React with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.