Lesson 12 Lab — Toasts and Error Handling, Staff and the Rest of the App¶
Two parts. First, retrofit your Staff CRUD (list, create/edit, delete) with
success/error toasts and the shared checkStatus/parseJSON helpers — the same
feedback the guide added to Menu Items, on your own entity. Then finish the job across
the app: the guide converted one API module and one set of screens, and left the rest
deliberately half-converted for you.
Refer back to the guide for the checkStatus helper and the try/success/catch shape.
Prerequisite: your API is running on the HTTP profile (
http, nothttps) with Staff seed data loaded, and you have the Staff CRUD from earlier lessons.
Part 1 — Staff¶
- Nothing to create —
src/utility/fetchUtilities.tsalready exists from the guide. - Refactor
StaffAPI.tsso every method chains.then(checkStatus).then(parseJSON)(deletejust.then(checkStatus)), importingBASE_URLfor theurl. One special case: the inlineif (!response.ok) throwyou wrote in Lesson 11'sfindByAccountbecomes a plain.then(checkStatus)— that hand-rolled guard was always a placeholder for this. - In
StaffPage'sloadStaff, wrap the fetch intry/catchandtoast.error(error.message, { duration: 6000 })on failure. - In
StaffForm'ssave,toast.success("Successfully saved.")after POST/PUT andtoast.error(error.message)in thecatch(with areturnso you stay on the form). - In the
StaffCarddelete handler (Lesson 6 lab), wrap thedelete+onRemovein atry/catch—toast.success("Successfully deleted.")on success,toast.error(error.message)in thecatch. Now thatstaffAPI.deleteruns throughcheckStatus, a failed delete throws. - ✅ Checkpoint: a save shows the green toast; a delete removes the card and toasts; with
the API stopped,
/staffshows a red error toast instead of a silent blank page.
Part 2 — the rest of the app¶
You've now done the whole pattern twice: once with the instructor on Menu Items, once on your own with Staff. What's left is mechanical repetition, and it's what makes the app consistent — right now three modules still swallow their errors.
The API modules — same three edits as Staff: import { BASE_URL, checkStatus, parseJSON },
switch const url to use BASE_URL, and chain .then(checkStatus).then(parseJSON) on every
method that returns data (.then(checkStatus) alone where there's no body — delete, and
workflow calls like cancel).
-
OrderAPI.ts -
OrderItemAPI.ts -
CategoryAPI.ts
The screens — now that every call throws, a fetch with no catch fails silently:
-
OrdersPage—loadOrdershas notry/catchat all. Add one (plusfinallyif you set a loading flag) and toasterror.message. This is the one that matters most: before this lesson a failed fetch quietly rendered an empty table; now it throws into nothing. -
CategoriesPage— swap theconsole.errorplaceholder you copied fromMenuItemsPagefor a toast.
The delete handlers — the ⋮ menus you wrote before this lesson call delete and then
onRemove with nothing guarding them. Give each the same try/catch treatment you just gave
StaffCard: success toast inside the try, toast.error(error.message) in the catch. Until
you do, a rejected delete throws unhandled and still removes the row from the list:
-
OrderRow(Lesson 6) -
CategoryCard(Lesson 9 lab)
Already correct, don't touch: OrderDetailPage, MenuItemForm, OrderItemForm, and
SignInPage all toast in their catch already — written that way in Lessons 8–11, they just
had no <Toaster /> to render into until Lesson 11. And ErrorPage's console.error stays
— it's the router's error boundary, not a fetch catch.
✅ Checkpoint: stop the API and load /orders, /categories, and /menuitems in turn —
each shows a red error toast rather than an empty table or blank grid. Restart the API, then
delete a category and an order — each confirms, removes the row, and toasts
"Successfully deleted."
Verify in the browser¶
Browser checks are covered in the guide — section 5. With your API running and
npm run dev up:
- Save a valid staff member → green "Successfully saved."; delete one → green "Successfully deleted."
- Stop the API and reload
/staff→ a red error toast, and the Console logs thehttp error statusdetail fromcheckStatus. Restart and confirm recovery. - Trigger a server rejection (e.g. a duplicate username, which is unique) on create → red toast, and you stay on the form.
- Toasts auto-dismiss and stack.
- With the API stopped, every list page —
/staff,/orders,/categories,/menuitems— toasts instead of failing silently. That consistency is the deliverable: one helper decides how failure reads, and no screen is exempt.
Same toast + centralized-error pattern, a different entity, then every entity — exactly how you'll add feedback to every PRS CRUD screen in the capstone.
Stretch challenges¶
Optional — for when you finish early. Not needed for the capstone. [Reinforce] builds on what you just did; [Reach] goes past the guide and needs some research.
- Position a toast — [Reinforce] — pass
{ position: "bottom-center" }to atoast.successand compare it to the default top position. Worth trying on a form reached from a detail page (the Order Item form), where a top toast can land far from where the user is looking. - A loading toast — [Reinforce] — replace a save's success/error with
toast.promise(staffAPI.post(staff), { loading: "Saving…", success: "Saved!", error: "Save failed" })so one call handles all three states. - Custom error messages — [Reinforce] — extend
translateStatusToErrorMessagewith a409case ("That username is already taken") and confirm a duplicate-username create surfaces it. - Dismiss on navigation — [Reach] — explore
toast.dismiss()to clear lingering toasts when leaving a page, and read the API surface for custom toasts. Reference: react-hot-toast — thetoast()API.
Finished these and want more? See stretch-react-challenges.md for bigger challenges that span the whole React pass.