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.
// 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.
// 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.