AniUI Academy

Context and useContext

Sharing a value across many components without threading it through every layer of props — createContext, Provider, useContext, and the re-render cost that limits it.

10 min read

You met the problem this lesson solves at the end of the "lifting state up" lesson: state shared by components that are many layers apart forces every component in between to accept and forward props it doesn't actually use itself. Context is React's mechanism for skipping that.

The three pieces

import { createContext, useContext } from "react";
 
// 1. Create the context (usually once, in its own module)
const ThemeContext = createContext("light");
 
// 2. Provide a value to a subtree
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}
 
// 3. Read it in any descendant, however deep
function Toolbar() {
  return <ThemedButton />; // Toolbar itself never touches theme
}
 
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={`btn-${theme}`}>Save</button>;
}

ThemedButton reads theme directly, despite Toolbar sitting in between and never mentioning theme at all — that's the whole payoff. The argument passed to createContext ("light" here) is only the fallback used if a component calls useContext with no matching Provider above it in the tree; whenever a Provider is present, its value wins.

Context pairs naturally with state

Providers commonly hand down not just a value but also a setter, so descendants can both read and update the shared value:

const ThemeContext = createContext(null);
 
function App() {
  const [theme, setTheme] = useState("light");
 
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}
 
function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
      Switch to {theme === "light" ? "dark" : "light"}
    </button>
  );
}

This is the same "lift state up, pass a setter down" pattern from before — context just changes how the value reaches descendants, from prop-by-prop to direct.

What context is genuinely good for

Context earns its keep for values that are: read by many components spread throughout the tree, and don't change especially often — the textbook examples are the current theme, the logged-in user, the active locale, or a router's current location. These are exactly the kind of "ambient" data that would otherwise force every component between the top of the tree and wherever it's needed to forward a prop it never uses.

The re-render cost

Context doesn't track which specific field of its value a given consumer actually reads — it only tracks whether the value passed to Provider changed at all. When it does, every component calling useContext for that context re-renders, whether or not the part it cares about actually changed.

function App() {
  const [user, setUser] = useState({ name: "Priya" });
  const [mouseX, setMouseX] = useState(0); // changes constantly, e.g. on mousemove
 
  // If these were combined into one context value, every mousemove
  // would re-render every component reading the context — including
  // ones that only ever cared about `user`.
  return (
    <UserContext.Provider value={user}>
      <MainApp />
    </UserContext.Provider>
  );
}

This is exactly why context is a poor fit for something that changes on every keystroke or every mouse move — every consumer downstream re-renders on every change, regardless of relevance. For high-frequency values, local state (kept as close as possible to where it's actually used) is the right tool; context is for comparatively stable, broadly-needed values.

Try it yourself

Try it yourself
Loading playground...

What to remember

  • Context is createContext + Provider + useContext — a way for any descendant to read a value directly, without every layer in between forwarding it as a prop.
  • It's the right tool for broadly-needed, comparatively stable values: theme, current user, locale.
  • Every consumer of a context re-renders whenever the Provider's value changes, regardless of which part of it a given consumer actually reads.
  • Context is a poor fit for high-frequency-changing values — keep those as local state instead.

Check yourself

4 questions · pass 3/4 to unlock useReducer for Complex State

up to 50
  1. 1.What problem does context solve that lifting state up doesn't solve well?

  2. 2.What three pieces does using context typically involve?

  3. 3.What happens to every component that calls useContext(SomeContext) when that context's Provider value changes?

  4. 4.Why is context described as unsuitable for something like frequently-updating form input state?

4 left to answer