Skip to content

The Context API

Prop drilling is when data passes through multiple layers of components that do not use it — only to reach a deeply nested component that does. Context is React’s solution: make data available to any component in the tree without threading it through every layer.

ZeroBudget passes budget data from App down to CategoryCard, which passes some of it down to TransactionList. If TransactionList needs the addTransaction function, App passes it to CategoryCard, which passes it to TransactionList — even though CategoryCard does not use addTransaction itself.

For three levels that is manageable. For six or eight levels it becomes unreadable. And when you add a new piece of data, you must update every intermediate component’s props — even the ones that just pass it through.

Context lets a parent component make data available to any descendant — skipping every intermediate layer. The data “broadcasts” down the tree. Any component that wants it subscribes directly, without involving the components between them.

Three pieces work together:

  1. createContext — creates a context object
  2. A Provider — wraps the tree and supplies the value
  3. useContext — subscribes a component to the context value
import { createContext } from 'react';
const BudgetContext = createContext(null);

null is the default value — what useContext returns when there is no Provider above it. Using null and checking for it (or throwing an error) catches components used outside their Provider.

The Provider wraps the part of the tree that needs the data:

function BudgetProvider({ children }) {
const budget = useBudget();
return (
<BudgetContext.Provider value={budget}>
{children}
</BudgetContext.Provider>
);
}

value is what every subscriber receives. It can be any value: a number, string, object, or — as in ZeroBudget — the entire return value of useBudget().

Any component inside the Provider can access the context:

import { useContext } from 'react';
function TransactionList() {
const { transactions, deleteTransaction } = useContext(BudgetContext);
// ... use them directly
}

No prop threading. The component reads what it needs directly from context.

When the Provider’s value changes, every component that called useContext with that context re-renders. This is why you should not put a new object literal directly in value — it would cause every subscriber to re-render on every render of the Provider’s parent:

// Bad — new object every render, all subscribers re-render
<BudgetContext.Provider value={{ budget, actions }}>
// Good — useBudget returns a stable-ish object (its contents change, but the re-render is correct)
const budget = useBudget();
<BudgetContext.Provider value={budget}>
  1. Create src/context/BudgetContext.jsx.
  2. Define BudgetContext with createContext(null).
  3. Write a BudgetProvider component that calls useBudget() and wraps children in BudgetContext.Provider.
  4. Wrap <App /> (or the content inside App) with <BudgetProvider>.
  5. In one deeply nested component, read a value with useContext(BudgetContext) instead of receiving it as a prop. Confirm it works.
  • Context lets a parent broadcast data to any descendant without threading props.
  • Three parts: createContext (creates the context), Provider (supplies the value), useContext (subscribes).
  • When the Provider’s value changes, all subscribers re-render.
  • Do not put new object literals in value — pass a stable reference or the result of a custom hook.