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.
The case for props
Section titled “The case for props”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.
When to reach for context
Section titled “When to reach for context”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.
Context anti-patterns
Section titled “Context anti-patterns”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.
The decision in ZeroBudget
Section titled “The decision in ZeroBudget”| Data | Where | Why |
|---|---|---|
category object | Prop | Identifies the specific card — each instance is different |
incomeSources | Context | Multiple components need it |
spentByCategory | Context | Every CategoryCard needs it; threading it through App would be messy |
addTransaction | Context | Called from CategoryCard and TransactionForm |
newCatName (form input) | Local state | Only the add-category form uses it |
Inline edit nameVal | Local state | Only 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.
Exercise
Section titled “Exercise”- Review your ZeroBudget component tree. List every prop being passed.
- For each prop, decide: should it come from context, stay as a prop, or be local state?
- Move any remaining shared data into context.
- For any data that is truly local to one component (an open/close toggle, a form input value), confirm it is in
useStateinside 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.