D
P
0

JavaScript

An Inline onclick stopPropagation Silently Broke Every Delegated Handler

June 14, 2026·3 min read
An Inline onclick stopPropagation Silently Broke Every Delegated Handler

This is one of the sneakiest bugs I've chased, because the symptom is no symptom at all. A favorite button (a heart icon) on a product card got clicked — and nothing happened. No UI change, no network request, no console error. The button felt alive (it had a hover state, a pointer cursor), but clicking it was like talking into a void.

What made it more confusing: the card's navigation didn't fire either, so at a glance it looked like the button was "catching" the click correctly. Yet not a single action was executing.

The HTML looked like this — a heart button nested inside the <a> that wraps the whole card:

<a href="/product/123" class="card">
  <!-- card contents -->
  <button
    class="favorite"
    data-id="123"
    onclick="event.preventDefault(); event.stopPropagation();"
  >

  </button>
</a>

That inline onclick was deliberately added to stop the card from navigating when the heart is clicked. Meanwhile, the actual save-favorite logic was handled by a delegated handler bound on document:

document.addEventListener("click", (e) => {
  const btn = e.target.closest(".favorite");
  if (!btn) return;
  e.preventDefault();
  e.stopPropagation();
  saveFavorite(btn.dataset.id); // this never runs
});

A reasonable pattern, right? The inline onclick stops navigation, the global handler saves the favorite. Except saveFavorite() was never being called.

Root cause: stopPropagation kills delegation

Here's what was actually happening. The inline onclick runs in the target phase — before the event gets a chance to bubble up to ancestors. When the inline handler calls event.stopPropagation(), it stops the event right there. The event never bubbles up to document. Since the delegated handler is bound on document and depends on bubbling to receive the event, that handler is never fired. The result: preventDefault() blocked navigation (which is why the card stayed put), but stopPropagation() also severed the chain to the global handler — so no favorite was ever saved.

I proved it directly in DevTools: remove the inline onclick attribute, click the button again — and it worked instantly. saveFavorite() fired, the request went out. That's the definitive confirmation that the inline onclick was the culprit.

The fix: drop stopPropagation, keep preventDefault

The fix is tiny. Just remove event.stopPropagation() from the inline onclick, keeping only event.preventDefault():

<button
  class="favorite"
  data-id="123"
  onclick="event.preventDefault();"
>

</button>

Why is this safe? Because the delegated handler on document does its own preventDefault() and stopPropagation() once it receives the event. So:

  • The inline preventDefault() still blocks the <a> navigation up front.
  • The event still bubbles up to document (there's no more stopPropagation in the inline).
  • The delegated handler receives the event, runs its logic, and calls preventDefault + stopPropagation at its own level.

Navigation is still blocked, and the favorite now saves.

Closing notes

  • Inline onclick runs in the target phase, before bubble-phase ancestor listeners. Whatever it calls happens first — including stopPropagation.
  • An inline stopPropagation kills delegated handlers bound on document. Event delegation depends entirely on bubbling; stop the bubble, stop the handler.
  • When using event delegation, audit templates for inline onclick that calls stopPropagation. That's the combination that quietly cancels itself out.
  • A "no error, no request" bug is almost always about event flow. No exception means the code never even ran — not that it failed while running.
  • Let the delegated handler manage its own preventDefault/stopPropagation. Don't duplicate it inline; just prevent the local default if you need to.

The core lesson: if you wire up logic through delegation on document, a single stopPropagation anywhere on the bubble path can silently take it down. Trace the event flow before you start guessing.