Back to roadmaps react Course

Context API and Global State

Passing props down through multiple layers of components to reach a deeply nested child is called Prop Drilling. This clutters intermediate components with unused props. React Context solves this by broadcasting data directly to any consuming component in the tree.


1. Creating and Using Context

To use Context, follow three simple steps:

  1. Create: Declare a Context object using createContext.
  2. Provide: Wrap parent components in the Context Provider to broadcast data.
  3. Consume: Access the value inside any child using the useContext hook.
import { createContext, useContext, useState } from "react";

// 1. Create Context
const ThemeContext = createContext(null);

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

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

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

// 3. Consume Context in a nested child
export function ThemeButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <button onClick={toggleTheme} className={`btn-${theme}`}>
      Current Mode: {theme}
    </button>
  );
}

2. Best Practices for Context Setup

When setting up global state providers, structure them to avoid cluttering your root component:

  • Export Custom Hooks: Instead of importing both the Context object and useContext hook in every component, export a custom hook that wraps the logic.
// Custom hook to consume ThemeContext safely
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }
  return context;
}

3. Performance Pitfalls: Unnecessary Re-Renders

Whenever the value passed to Context.Provider changes, all components that consume that Context will re-render.

If you pass a combined object containing multiple state values, updates to any single property will trigger re-renders even in components that only consume the unchanged fields:

// Bad Performance Setup
<AppContext.Provider value={{ user, theme, preferences }}>

Mitigation Strategies

  • Split Contexts: Create dedicated providers for unrelated states (for example, keep UserProvider separate from ThemeProvider).
  • Memoize Values: Use useMemo to cache the provider value object so it only changes when necessary.
Published on Last updated: