Skip to content

React Context API — Global State Management

DodaTech 3 min read

In this tutorial, you'll learn about React Context API. We cover key concepts, practical examples, and best practices.

What You'll Learn

Use React's Context API to share global state across components — create context, provide values, consume with useContext, optimize performance, and avoid prop drilling.

Why It Matters

Without Context, you pass data through every level of the component tree (prop drilling). Context gives any component direct access to shared data.

Real-World Use

Sharing a user's authentication status, theming (light/dark mode), locale/language preferences, or a shopping cart across the entire app.

The Prop Drilling Problem

// ❌ Without Context — prop drilling
function App() {
  const [user, setUser] = useState(null);
  return <Header user={user} />;
}

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

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

function Avatar({ user }) {
  return <img src={user.avatar} alt={user.name} />;
}

// ✅ With Context — direct access
function Avatar() {
  const { user } = useContext(UserContext);
  return <img src={user.avatar} alt={user.name} />;
}

Creating and Using Context

import { createContext, useContext, useState } from "react";

// Step 1: Create context
const AuthContext = createContext(null);

// Step 2: Create provider component
function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  function login(email, password) {
    // In reality, call an API
    setUser({ email, name: "Alice" });
  }

  function logout() {
    setUser(null);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Step 3: Custom hook for consuming
function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within AuthProvider");
  }
  return context;
}

// Step 4: Use it anywhere
function LoginButton() {
  const { user, login, logout } = useAuth();

  if (user) {
    return (
      <div>
        Welcome, {user.name}
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  return <button onClick={() => login("a@b.com", "pass")}>Login</button>;
}

// Step 5: Wrap your app
function App() {
  return (
    <AuthProvider>
      <Header />
      <MainContent />
    </AuthProvider>
  );
}

Theme Context Example

const ThemeContext = createContext("light");

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  const toggleTheme = () => {
    setTheme(prev => prev === "light" ? "dark" : "light");
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === "dark" ? "#333" : "#fff",
        color: theme === "dark" ? "#fff" : "#333",
        padding: "10px",
        border: "1px solid #ccc",
      }}
    >
      Switch to {theme === "light" ? "Dark" : "Light"} Mode
    </button>
  );
}

Multiple Contexts

function App() {
  const [theme, setTheme] = useState("light");
  const [user, setUser] = useState(null);
  const [cart, setCart] = useState([]);

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <AuthContext.Provider value={{ user, setUser }}>
        <CartContext.Provider value={{ cart, setCart }}>
          <MainApp />
        </CartContext.Provider>
      </AuthContext.Provider>
    </ThemeContext.Provider>
  );
}

Performance Optimization

Context re-renders ALL consumers when the value changes:

// ❌ Problem: every keypress re-renders all context consumers
function App() {
  const [text, setText] = useState("");

  return (
    <SearchContext.Provider value={{ text, setText }}>
      <ExpensiveComponent />  {/* Re-renders on every keypress */}
    </SearchContext.Provider>
  );
}

// ✅ Solution: split contexts or use useMemo
function App() {
  const [text, setText] = useState("");

  const searchValue = useMemo(
    () => ({ text, setText }),
    [text]
  );

  return (
    <SearchContext.Provider value={searchValue}>
      <ExpensiveComponent />
    </SearchContext.Provider>
  );
}

Context vs Redux

Aspect Context Redux
Complexity Simple More setup
Boilerplate Minimal Actions, reducers, store
Performance All consumers re-render Selectors prevent re-renders
Middleware None Redux Thunk, Saga
DevTools Basic Excellent
Scale Small-medium apps Large apps
Learning curve Low Medium

When to Use Context

✅ User authentication / session
✅ Theme (light/dark mode)
✅ Language / locale preferences
✅ Feature flags
✅ Small-medium apps without complex state

❌ Frequently updating data (consider Redux, Zustand)
❌ Complex state logic (consider useReducer + Context)
❌ Very large apps (consider Redux or Zustand)

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro