Master Code
On The Go

Learn. Practice. Build.

Home › How-To › React Context API

React Guide

React Context API

Share state across components without prop drilling.

Last updated: October 2026 · Tested on React 18+

Quick answer: Use createContext() to define a context, wrap components in a <Provider> with a value prop, and read the value anywhere with useContext(). Use Context for global data like theme, auth, or language. Wrap the value object in useMemo to avoid unnecessary re-renders.

1

The Problem: Prop Drilling

Without Context, passing data to a deeply nested component means threading props through every level:

function App() {
    const user = { name: 'Alice' };
    return <Layout user={user} />;
}

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

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

function UserMenu({ user }) {
    return <p>{user.name}</p>;
}

Four components pass the same prop down. This is prop drilling. It's noisy and painful to refactor.

2

Step 1: Create the Context

import { createContext } from 'react';

export const UserContext = createContext(null);

The argument is the default value. In practice, you'll always provide a value, so this is mostly for TypeScript and safety.

3

Step 2: Provide the Value

import { useState, useMemo } from 'react';
import { UserContext } from './UserContext';

export function UserProvider({ children }) {
    const [user, setUser] = useState({ name: 'Alice' });

    const value = useMemo(() => ({ user, setUser }), [user]);

    return (
        <UserContext.Provider value={value}>
            {children}
        </UserContext.Provider>
    );
}

💡 Why useMemo matters: Without it, the value object is recreated on every render of the provider, forcing every consumer to re-render even if the data hasn't changed.

4

Step 3: Consume the Value

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

function UserMenu() {
    const { user, setUser } = useContext(UserContext);

    return (
        <div>
            <p>Welcome, {user.name}</p>
            <button onClick={() => setUser({ name: 'Bob' })}>
                Switch user
            </button>
        </div>
    );
}

Any component inside the UserProvider can read and update the user. No prop drilling.

5

Step 4: Wire It All Together

function App() {
    return (
        <UserProvider>
            <Layout />
        </UserProvider>
    );
}

function Layout() {
    return (
        <div>
            <Sidebar />
            <Main />
        </div>
    );
}

function Sidebar() {
    return <UserMenu />; // No prop drilling
}
6

Creating a Custom Hook for Cleaner Code

Wrap useContext in a custom hook so every component has a consistent API and TS types:

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

export function useUser() {
    const context = useContext(UserContext);
    if (!context) {
        throw new Error('useUser must be used within a UserProvider');
    }
    return context;
}

// Usage in components
const { user, setUser } = useUser();

This is the recommended pattern. It gives you a clear error if someone uses the hook outside the provider, and TypeScript can infer the types.

🎯 When to Use Context (and When Not To)

✅ Good uses of Context

  • Theme (dark/light mode)
  • Current user / auth state
  • Language / locale
  • Shopping cart
  • Feature flags
  • Toast/notification system

❌ Bad uses of Context

  • Every piece of app state
  • High-frequency updates (mouse position)
  • Form state
  • Data that only one component needs
  • Complex state machines (use Redux/Zustand)

📊 Context vs Redux vs Zustand

Feature Context Redux Zustand
Boilerplate Minimal High Minimal
Dev tools None Excellent Good
Performance Re-renders all consumers Optimized selectors Optimized selectors
Best For Simple global state Large enterprise apps Modern apps, small to medium

🛡️ Best Practices

Common mistake: Passing an object literal directly to the Provider's value prop. Every render of the provider creates a new object, causing every consumer to re-render. Always wrap it in useMemo.

❓ Frequently Asked Questions

What is the React Context API?

The React Context API lets you share data across the component tree without passing props down manually at every level. It solves the 'prop drilling' problem for things like themes, auth, and language.

When should I use React Context?

Use Context for global-ish data that many components need: current user, theme, language, shopping cart. Avoid using it for local state or high-frequency updates, which cause performance problems.

Is Context a replacement for Redux?

No. Context is a delivery mechanism, not a state manager. Redux provides structure, dev tools, middleware, and performance optimizations. For simple global state, Context is enough. For complex apps, Redux or Zustand is often better.

Why is my Context causing re-renders?

Every component that consumes a Context re-renders when the Context value changes. If you pass an object literal to the Provider's value prop, it creates a new object on every render and re-renders all consumers. Wrap the value in useMemo.

Should I have one big Context or many small ones?

Use many small contexts. Split by concern — one for auth, one for theme, one for language. This limits re-renders to only the components that care about the specific piece of data.

🎯 What's Next?

← All How-To Guides