This was one of the most deceptive bugs I ran into while building the CIVA admin dashboard.
I was testing the product creation wizard: I opened the modal, selected a category, typed a test product title, and changed my mind. I hit the "Cancel" button. The modal closed cleanly.
A minute later, I opened the system audit log. Right at the top:
Admin User updated draft "Test Motherboard" (Status: Pending)
Even worse, on the main table, a ghost product row with half-filled attributes was sitting there until I hard-refreshed the browser.
Cancel hadn't canceled anything. It had silently committed dirty state behind my back.
What Actually Happened
When building complex forms, developers often try to be helpful. I had written a helper to prevent users from losing work if they accidentally closed a modal:
// The well-intentioned trap
const handleClose = () => {
if (isDirty) {
syncDraftToBackend(formData); // "Helpful" auto-save
}
onClose();
};
The fatal flaw was using handleClose for both the explicit "Save & Exit" button and the "Cancel" button (as well as the Escape key listener).
By conflating "dismissing without saving" with "safely preserving uncommitted drafts", any half-typed junk entered into the modal was treated as a valid draft update.
The Realization: Explicit Intent Must Be Respected
An interface should never guess whether a user wants to discard or save. When someone clicks Cancel, their intent is unambiguous: throw this away.
I split the modal lifecycle into three distinct handlers:
handleCancel(): Explicitly wipes local state, aborts in-flight network requests, and triggers zero backend mutations.handleSaveDraft(): An explicit, dedicated action that validates the bare minimum schema before syncing.handleCommit(): The final submission step with complete field validation.
// Clean, predictable separation
const handleCancel = () => {
resetFormState();
abortPendingRequests();
onClose();
};
The Takeaway
Defensive UX—like auto-saving uncommitted work—must never override explicit user commands.
When a user clicks Cancel, the system should behave as if the interaction never took place. No network calls, no audit entries, no residual cache. True simplicity means predictable side effects.