Skip to content
intermediate Phase 8 · React

Context API

Share state across components without prop drilling using React Context.

45m
0 problems
Topic Progress 0%

Creating Context

Creating Context

Context provides a way to pass data through the component tree without props drilling.

Basic Context

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

// Create context
const ThemeContext = createContext(null);

// Provider component
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>
  );
}

// Custom hook for using context
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }
  return context;
}

// Usage
function App() {
  return (
    <ThemeProvider>
      <Header />
      <Main />
    </ThemeProvider>
  );
}

function Header() {
  const { theme, toggleTheme } = useTheme();
  return (
    <header>
      <button onClick={toggleTheme}>Toggle Theme</button>
      <span>Current: {theme}</span>
    </header>
  );
}

Default Values

// With default value
const ThemeContext = createContext("light");

// Without provider, uses default value
function Component() {
  const theme = useContext(ThemeContext); // "light"
}

Multiple Contexts

const AuthContext = createContext(null);
const ThemeContext = createContext(null);

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

Provider Pattern

Provider Pattern

Complete Provider Example

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

const AuthContext = createContext(null);

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const login = async (email, password) => {
    try {
      setLoading(true);
      setError(null);
      const response = await fetch("/api/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password })
      });
      const data = await response.json();
      setUser(data.user);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const logout = () => {
    setUser(null);
  };

  // Memoize value to prevent unnecessary re-renders
  const value = useMemo(
    () => ({
      user,
      loading,
      error,
      login,
      logout,
      isAuthenticated: !!user
    }),
    [user, loading, error]
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

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

Nested Providers

function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <NotificationProvider>
          <Layout />
        </NotificationProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

// Each component can access any provider
function Header() {
  const { user } = useAuth();
  const { theme } = useTheme();
  const { notifications } = useNotification();
}

Provider Composition

// Combine multiple providers
function AppProviders({ children }) {
  return (
    <AuthProvider>
      <ThemeProvider>
        <NotificationProvider>
          {children}
        </NotificationProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

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

useContext Hook

useContext Hook

Basic Usage

const ThemeContext = createContext("light");

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click me</button>;
}

Accessing Context Values

function UserProfile() {
  const { user, logout } = useAuth();
  const { theme, toggleTheme } = useTheme();

  if (!user) return <p>Please log in</p>;

  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={logout}>Logout</button>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}

Context with Performance

// Problem: All consumers re-render when any value changes
const ValueContext = createContext({ a: 0, b: 0 });

// Solution: Split into separate contexts
const AContext = createContext(0);
const BContext = createContext(0);

function Provider({ children }) {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);

  return (
    <AContext.Provider value={a}>
      <BContext.Provider value={b}>
        {children}
      </BContext.Provider>
    </AContext.Provider>
  );
}

// Now components only re-render when their specific value changes
function ComponentA() {
  const a = useContext(AContext); // Only re-renders when a changes
}

function ComponentB() {
  const b = useContext(BContext); // Only re-renders when b changes
}

Context vs Redux

Feature Context Redux
Complexity Simple Complex
Bundle size Small Larger
DevTools No Yes
Middleware No Yes
Performance Good for simple Optimized

Use Context for:

  • Theme, language, auth
  • Simple global state
  • Application settings

Use Redux for:

  • Complex state logic
  • Frequent updates
  • Need for devtools/middleware

Quiz

1. What is React Context used for?

Question 1 options

2. What does useContext return?

Question 2 options

3. What happens when context value changes?

Question 3 options

4. When should you use Context?

Question 4 options

Flashcards

Question

What is React Context?

Answer

A way to pass data through component tree without props drilling

Question

What does useContext return?

Answer

The value of the nearest Context Provider

Question

When do context consumers re-render?

Answer

When the context value changes

Question

When should you use Context vs Redux?

Answer

Context for simple global state, Redux for complex state logic

Revision Notes

Key Takeaways

  • 1. Context avoids props drilling
  • 2. useContext consumes context values
  • 3. All consumers re-render when value changes
  • 4. Split contexts for performance
  • 5. Use for simple global state

Interview Tips

  • Explain when to use Context vs Redux
  • Show how to create a Context Provider
  • Demonstrate avoiding props drilling

Cheat Sheet

Cheat Sheet

Create Context

const ThemeContext = createContext(null);

Provider

<ThemeContext.Provider value={{ theme, toggle }}>
  {children}
</ThemeContext.Provider>

useContext

const { theme, toggle } = useContext(ThemeContext);

Custom Hook

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error("...");
  return context;
}