JS Sketchbooksee JavaScript think ✏️
← back to phase 7

7.5 — Bubbling, delegation & preventDefault

Last lesson ended on a cliffhanger: an event doesn't just hit its target — it travels through the whole tree on the way there and back. Today: the full journey, and the single most useful trick it quietly enables — delegation: one listener instead of a hundred.

Plus a separate tool that gets confused with it constantly: preventDefault(), which stops the browser's own reaction to an event — a link navigating, a form submitting — and has nothing to do with the event's travel at all.

watch it happen
const list = document.querySelector("#list");

list.addEventListener("click", (event) => {
  console.log("heard on: " + event.currentTarget.id);
  console.log("actually clicked: " + event.target.tagName);

  if (event.target.matches(".delete")) {
    event.target.closest("li").remove();
  }
});

// <ul id="list">
//   <li>milk <button class="delete">×</button></li>
//   <li>eggs <button class="delete">×</button></li>
// </ul>

A click lands on the delete button, nested inside an li, inside a ul. Does ONLY the button react — or do its ancestors get a say too?

the event's journey through the treedocumentul#list — LISTENER HERElibutton.delete — clickedclick the delete button on "milk" — does only IT react, or do its ancestorstoo?(console: nothing yet)
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

Precision for interviews: addEventListener's rare third argument (true or { capture: true }) opts a listener INTO the capture phase instead of bubble. Almost every real listener uses the default — bubble — which is exactly why delegation works at all: it needs the event to still be traveling upward when it arrives.

event.currentTarget is only meaningful WHILE the handler is actively running — the DOM reassigns it fresh at each node the event visits. Save the whole event object for later and read currentTarget then, and you'll find it's gone (usually null). Read it synchronously, inside the handler, always.

Job note: Playwright's .click() dispatches a real, trusted click event that bubbles exactly like a human's would — which is precisely why delegated listeners in the app you're testing keep firing correctly under automation, with zero special handling.

your turn

⌨️ delegate, don’t multiply

Build the delegation pattern in miniature: ONE handler that receives which child fired and removes it — modeled with plain objects and arrays (no real DOM in this sandbox).

requirements:

  • An array items of objects: { id: "milk" }, { id: "eggs" }, { id: "bread" }.
  • A function handleDelegatedClick(items, clickedId): return a NEW array with the matching item removed (4.9’s filter — one function, works for ANY id).
  • Call it with "eggs", print the remaining ids joined by ", ". Then call it AGAIN on that result with "bread" — the SAME function handling a completely different click, proving delegation needs no per-item logic. Print the final ids.

when you press RUN, the console must show exactly:

milk, bread
milk

✏️ Quick check 1

In the handler, event.target is the button. Type exactly what event.target.tagName prints:

✏️ Quick check 2

Does stopPropagation() also stop the browser’s default action (like a link navigating)? Type yes or no.

✏️ Quick check 3

A brand-new <li> is added to the list AFTER the listener was wired to the ul. Does clicking its delete button still work, with no new listener? Type yes or no.

teach it back

🗣️ Now teach it back

Explain to a friend the three phases every event travels through, what delegation is and why it actually works, and the difference between stopPropagation and preventDefault.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Every event travels CAPTURE (down) → TARGET → BUBBLE (up); nearly all listeners run in the bubble phase.
Delegation: one listener on a parent handles every child — present AND future — via bubbling. event.target = what was hit; event.currentTarget = where the listener lives.
stopPropagation halts the bubble (rare, breaks delegation elsewhere); preventDefault cancels the browser’s own default action. Different jobs, often confused.
next: 7.6 Forms & user input →