useContext and Providers
With the Provider in place, consuming components can drop prop threading entirely. This lesson completes ZeroBudget’s context layer and shows the custom context hook pattern.
The custom context hook
Section titled “The custom context hook”Every consuming component calling useContext(BudgetContext) directly has two problems: it imports both useContext and BudgetContext, and it has no protection against being used outside the Provider. A custom hook solves both:
import { createContext, useContext } from 'react';import { useBudget } from '../hooks/useBudget';
const BudgetContext = createContext(null);
export function BudgetProvider({ children }) { const budget = useBudget(); return <BudgetContext.Provider value={budget}>{children}</BudgetContext.Provider>;}
export function useBudgetContext() { const ctx = useContext(BudgetContext); if (!ctx) throw new Error('useBudgetContext must be used inside BudgetProvider'); return ctx;}Consumers call useBudgetContext() — one import, built-in error message if misused, no raw useContext needed in component files.
Wiring all consumers
Section titled “Wiring all consumers”With the context hook in place, components become much simpler. Before:
// CategoryCard.jsx — before contextexport default function CategoryCard({ category, spentByCategory, onUpdate, onDelete, onAddTransaction, onDeleteTransaction }) { const spent = spentByCategory[category.id] || 0; // ...}After:
// CategoryCard.jsx — after contextexport default function CategoryCard({ category }) { const { spentByCategory, updateCategory, deleteCategory, addTransaction, deleteTransaction } = useBudgetContext(); const spent = spentByCategory[category.id] || 0; // ...}CategoryCard now only receives category as a prop — the data about which card it is. Everything else comes from context. App.jsx no longer needs to thread seven props through the categories grid.
What App.jsx looks like after
Section titled “What App.jsx looks like after”import { BudgetProvider, useBudgetContext } from './context/BudgetContext';
function AppContent() { const { categories, addCategory } = useBudgetContext(); const [newCatName, setNewCatName] = useState('');
function handleAddCategory(e) { e.preventDefault(); if (!newCatName.trim()) return; addCategory(newCatName.trim()); setNewCatName(''); }
return ( <div className="app"> <MonthNav /> <div className="app-top"> <IncomeSection /> <LeftToAssign /> </div> <section className="categories-section"> <div className="categories-header"> <h2>Budget Categories</h2> <form onSubmit={handleAddCategory}> <input value={newCatName} onChange={e => setNewCatName(e.target.value)} placeholder="New category" /> <button type="submit">Add</button> </form> </div> <div className="categories-grid"> {categories.map(cat => <CategoryCard key={cat.id} category={cat} />)} </div> </section> <TransactionForm /> </div> );}
export default function App() { return ( <BudgetProvider> <AppContent /> </BudgetProvider> );}App is a layout component. No state, no handlers — just structure. Every component reads what it needs from context.
Multiple contexts
Section titled “Multiple contexts”An app can have multiple contexts — auth, theme, notifications, budget. Each context has its own Provider. Nest them from outermost (app-wide) to innermost (feature-specific):
<AuthProvider> <ThemeProvider> <BudgetProvider> <AppContent /> </BudgetProvider> </ThemeProvider></AuthProvider>Exercise
Section titled “Exercise”- Update
BudgetContext.jsxwith theuseBudgetContexthook that throws if used outside the Provider. - Remove all budget-related props from
IncomeSection,LeftToAssign,CategoryCard,TransactionForm, andMonthNav. Have each component calluseBudgetContext()directly. - Update
App.jsxto wrap with<BudgetProvider>and remove all prop passing. - Confirm the app still works end-to-end.
- Create a
useBudgetContexthook that callsuseContextand throws if used outside the Provider. - Consumers import one hook — no raw
useContextor context object in component files. - Components receive only the props that differentiate them (like
category); shared data comes from context. - Multiple contexts nest — outermost is broadest scope.