Add a Category Safely
Open:
server/src/routes/categories.js
client/src/lib/api.js
client/src/context/BudgetContext.jsx
client/src/pages/CategoriesPage.jsx
Adding a category is a small create workflow: read a name, send it to the API, receive the canonical category object, then update shared state.
Trim the submitted name before doing any checks:
const name = typeof req.body?.name === "string"
? req.body.name.trim()
: "";
if (!name) {
return res.status(400).json({ error: "name is required" });
}
This prevents a category made only of spaces.
Now treat capitalization as presentation rather than identity:
const existing = db.categories.find(
(category) => category.name.toLowerCase() === name.toLowerCase()
);
if (existing) {
return res.status(200).json(existing);
}
If Travel already exists, submitting travel returns the same category instead of creating a second one.
When the name is new, create and persist the category:
const category = { name, color };
db.categories.push(category);
await saveDb();
res.status(201).json(category);
The API helper sends the request:
export const addCategory = (name) =>
request("/categories", {
method: "POST",
body: JSON.stringify({ name }),
});
The context updates its shared collection using the returned category:
async function addCategory(name) {
const category = await api.addCategory(name);
setCategories((previous) =>
previous.some(
(item) => item.name.toLowerCase() === category.name.toLowerCase()
)
? previous
: [...previous, category]
);
return category.name;
}
The page should clear the input only after the request succeeds. That way a failed request does not erase what the user typed.
Test it
Add Travel.
Add travel again. The second action should not create a duplicate category.
Refresh the page and confirm the category remains.
Checkpoint
A category name is trimmed, duplicate names are handled consistently, the server owns the canonical category object, and successful creation updates shared client state.