Skip to content

Context vs Props

Context solves prop drilling. But used too broadly, it creates a different problem: hidden dependencies that make components hard to understand and reuse. Knowing when to use each tool is as important as knowing how.

Props are explicit. When you read a component, you can see exactly what data it receives. When you render it, you can see exactly what you are passing. Props make dependencies visible.

Props are also flexible. A component that takes props can be rendered anywhere in any app — with different values each time. A component that reads from context is coupled to whatever provides that context. Remove or change the Provider and the component breaks.

For most data, props are correct: they are direct, visible, and composable.

Context is appropriate when:

1. The data is truly global to a subtree. The current user, authentication status, theme, locale — data that every component in the tree needs. Passing these as props through every layer is noise.

2. Prop drilling reaches three or more levels. If data passes through two intermediate components that do not use it, context removes useless prop threading. One or two levels is not worth the coupling.

3. The data changes infrequently. Context re-renders every subscriber when it changes. For data that changes often (every keystroke, every animation frame), context can cause performance issues. Local state or a more targeted solution is better.

Using context for everything. If every piece of state lives in context, you get a God Object: one enormous, constantly-changing value that causes every component to re-render constantly.

Putting frequently-changing values in context. A search input value that updates on every keystroke belongs in local state — not context. Putting it in context re-renders every subscriber on every keystroke.

Replacing all props with context. A CategoryCard that takes category as a prop is still correct — it identifies which card instance this is. The budget data (totals, actions) comes from context because all cards need it. Using context for both conflates different concerns.

DataWhereWhy
category objectPropIdentifies the specific card — each instance is different
incomeSourcesContextMultiple components need it
spentByCategoryContextEvery CategoryCard needs it; threading it through App would be messy
addTransactionContextCalled from CategoryCard and TransactionForm
newCatName (form input)Local stateOnly the add-category form uses it
Inline edit nameValLocal stateOnly the editing row cares about it

The pattern: context for shared data and shared actions, props for identity and differentiation, local state for UI-only ephemeral values.

  1. Review your ZeroBudget component tree. List every prop being passed.
  2. For each prop, decide: should it come from context, stay as a prop, or be local state?
  3. Move any remaining shared data into context.
  4. For any data that is truly local to one component (an open/close toggle, a form input value), confirm it is in useState inside that component — not in context.
  • Props are explicit and flexible — use them by default.
  • Context is for data that is global to a subtree and changes infrequently.
  • Avoid putting frequently-changing values in context — they cause performance issues.
  • The pattern in ZeroBudget: context for shared state and actions, props for instance identity, local state for UI-only values.