Render Categories from the Shared Data
Open:
client/src/context/BudgetContext.jsx
client/src/pages/CategoriesPage.jsx
server/src/routes/categories.js
A category is reusable metadata. In this project it has a name and a color. A transaction stores the category name it uses when the transaction is an expense.
The shared context loads categories alongside transactions:
const [tx, cats] = await Promise.all([
api.getTransactions(),
api.getCategories(),
]);
setTransactions(tx);
setCategories(cats);
The Categories page reads both collections through useBudget():
const { categories, transactions } = useBudget();
The page then derives usage statistics. These values do not need to be stored in the database because they can always be calculated from transactions.
Start with an empty lookup object:
const byCategory = {};
Walk through the transactions and skip income:
transactions.forEach((transaction) => {
if (transaction.type !== "expense") return;
if (!byCategory[transaction.category]) {
byCategory[transaction.category] = {
total: 0,
count: 0,
};
}
byCategory[transaction.category].total += transaction.amount;
byCategory[transaction.category].count += 1;
});
The first time a category appears, create its accumulator. Every later transaction adds to that category’s total and entry count.
Then combine the derived values with the server-owned category object:
return categories.map((category) => ({
...category,
total: byCategory[category.name]?.total ?? 0,
count: byCategory[category.name]?.count ?? 0,
}));
...category preserves the original name and color. The total and count values are calculated view data.
Test it
Open the Categories page and compare its category names and colors with server/data/db.json.
For Housing, count the expense transactions that use Housing and compare that number with the displayed entry count.
Checkpoint
The category list comes from server data, while entry counts and spending totals are derived from the shared transaction collection.