Context Minimal

Introduce React Context only for truly global, read‑heavy data; never for high‑frequency changing state.

Why?  Limits blanket re‑renders and keeps dependency graph clear.

Because every Context update forces all subscribed components to re‑render, stuffing fast‑changing values (typing, mouse position, live timers) into Context can tank performance. Teams sometimes reach for Context as a shortcut to avoid prop‑drilling and end up with an app that re‑renders on every keystroke. Context shines for information that is needed everywhere and changes seldom—think theme, locale, or the current authenticated user.

Correct Example

// ThemeContext.tsx
import { createContext, ReactNode, useContext, useState } from "react";

type Theme = "light" | "dark";
const ThemeContext = createContext<Theme>("light");

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light"); // infrequent changes

  return (
    <ThemeContext.Provider value={theme}>
      <button
        onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}
        className="fixed right-4 top-4 rounded bg-stone-800 px-3 py-1 text-white"
      >
        Toggle Theme
      </button>
      {children}
    </ThemeContext.Provider>
  );
}

export const useTheme = () => useContext(ThemeContext);
// Button.tsx — consumes Context without prop‑drilling
import { useTheme } from "./ThemeContext";

export default function Button({ children }: { children: React.ReactNode }) {
  const theme = useTheme();
  const style =
    theme === "dark"
      ? "rounded bg-stone-700 px-4 py-2 text-white"
      : "rounded bg-stone-200 px-4 py-2 text-stone-900";

  return <button className={style}>{children}</button>;
}

Why this is good Theme toggles are rare, so re‑rendering every themed component is cheap and predictable.

Incorrect Example

// SearchContext.tsx — puts a per‑keystroke value in Context
import { createContext, ReactNode, useContext, useState } from "react";

const SearchContext = createContext<string>("");

export function SearchProvider({ children }: { children: ReactNode }) {
  const [query, setQuery] = useState(""); // ⚠️ updates every keypress
  return (
    <SearchContext.Provider value={query}>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search…"
        className="mb-4 w-full rounded border p-2"
      />
      {children}
    </SearchContext.Provider>
  );
}

export const useSearch = () => useContext(SearchContext);
function ProductList({ products }: { products: Product[] }) {
  const query = useSearch(); // re‑renders constantly
  const filtered = products.filter((p) =>
    p.name.toLowerCase().includes(query.toLowerCase())
  );
  /* render list */
}
// Every character typed re‑renders ProductList and ANY other consumer,
// even ones that don’t care about the search query.

Key Takeaways

  • Reserve Context for global, rarely changing values (theme, locale, auth user, feature flags).
  • Keep rapid updates local—pass them down via props or use a state library that slices updates to subscribers.
  • Changing Context = blanket re‑render: Profile before promoting state to Context.
  • **If you feel Context overuse creeping in, reach for custom hooks, prop composition, or specialized stores instead.