DevAcademy
LearnReactContext API
IntermediateReact

Context API

Learn how to share data across a component tree without threading props through every level, using React Context.

Reading Time

18 min

Lesson

Lesson 16 of 42

The Problem: Deep Prop Drilling

Some data — like the current theme, logged-in user, or language — is needed by many components at very different depths in the tree. Passing it down as props through every intermediate component (prop drilling) becomes tedious and fragile as the app grows.

Creating a Context

createContext() creates a Context object. A Provider component makes a value available to every component nested inside it, no matter how deep.

Creating and Providing a Context

import { createContext } from "react";

const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

Reading a Context Value

Any component nested inside the Provider can read the current value with the useContext hook, regardless of how many components sit between it and the Provider.

Consuming Context with useContext

import { useContext } from "react";

function Toolbar() {
  return <ThemedButton />; // doesn't need to know about ThemeContext at all
}

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

Combining Context with useState

Context is most useful when combined with state — the Provider’s value can include both the current data and a function to update it, giving any nested component read and write access.

A Context with Updatable State

const UserContext = createContext(null);

function App() {
  const [user, setUser] = useState({ name: "Alice" });

  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Dashboard />
    </UserContext.Provider>
  );
}

function Dashboard() {
  const { user, setUser } = useContext(UserContext);
  return <button onClick={() => setUser({ name: "Bob" })}>{user.name}</button>;
}

Every Consumer Re-Renders on Change

When a Provider’s value changes, every component consuming that context re-renders, even if it only cares about part of the value. For frequently-changing, performance-sensitive state, a dedicated state management library or splitting into smaller contexts may be a better fit.

Context is Not a Replacement for All Props

Context works best for data considered "global" to a whole subtree — theme, auth, locale. For data that only a couple of nearby components need, regular props (or lifting state up) is usually simpler and easier to trace.

Best Practice

Wrap the Provider and its useContext logic in a small custom hook (e.g. useTheme()) so consuming components import one clean function instead of dealing with useContext(ThemeContext) directly everywhere.

Interview Questions

Quick Quiz

1. What problem does the Context API solve?

2. Which hook is used to read a context value inside a component?

3. What happens to a context’s consumers when its Provider value changes?