Delete Flow and UX Polish
Introduction: You Made It — Now Let’s Finish Like Pros 🏁
Huge congrats for making it this far. Seriously. 🙌 You’ve already built the core product loop of a real task manager: users can create tasks, edit them, and update their completion status with fast, responsive UI.
In this final lesson, you’ll add the kind of polish that makes an app feel production-ready instead of just functional. That means safe deletes with a confirmation modal so users do not lose data by mistake, global loading and error UI for the whole dashboard segment so the app feels stable and predictable, and a more cohesive data strategy so lists, filters, and detail pages stay in sync after every action.
As always, one rule stays in place: do not modify anything under src/app/api/**. The backend is read-only. Your job here is to perfect the frontend experience on top of the existing API.
Recall: The “Danger Action” Pattern (and why deletes are special)
You’ve already seen the general product loop for actions like Create and Update. You call the API client through api.*, show toast feedback, revalidate SWR keys with mutate(...), and navigate the user to the most sensible next screen.
Deletes follow that same pattern, but they need one extra step first: confirmation. That is because delete actions are destructive and usually irreversible. A mistaken create or edit can often be fixed later, but a mistaken delete is much more costly. That is why professional apps slow the user down just enough before destructive actions by asking for confirmation.
That extra confirmation step is exactly why we add a modal in this lesson.
Practice 1: Delete Confirmation Modal + Delete Flow on the Detail Page
This code block is the heart of the safe delete experience. It introduces a reusable <Modal /> component and wires the /tasks/[id] page so users must confirm before deleting. On success, the app shows a toast, revalidates task data, and navigates back to /tasks.
The reusable Modal component gives you a consistent confirmation dialog anywhere in the app. It handles Escape-to-close, renders an overlay and centered panel, and provides clear “Cancel” and “Delete” actions.
-
isOpencontrols whether the modal is mounted at all. When it isfalse, the modal returnsnull, which means it cannot intercept clicks, appear visually, or interfere with the rest of the page. That is what makes it behave like a true modal rather than just a hidden block of markup. -
Escape-to-close is a small but important UX detail. It makes the component feel more native and more forgiving, because users have a fast keyboard-based way to back out of the confirmation. Good modal behavior is not only about appearance — it is also about giving users easy ways to cancel.
-
The component is reusable by design because the message content comes from
children. Today you are using it for delete confirmation, but the same modal structure can support other confirmation flows later without rewriting the overlay and panel behavior each time.
