preventDefault vs stopPropagation
Cancel browser defaults vs stop event bubbling — capture, stopImmediatePropagation, and when passive listeners ignore preventDefault.
- javascript
- preventdefault-vs
Click a link — browser navigates. Submit a form — browser posts. Those are default actions. Events also propagate through the DOM (capture → target → bubble). Two different APIs control those two behaviors, and interviews mix them up constantly.
preventDefault — cancel the default action
link.addEventListener('click', (e) => {
e.preventDefault();
// SPA route change instead of full navigation
router.push(link.getAttribute('href'));
});
form.addEventListener('submit', (e) => {
e.preventDefault();
submitWithFetch(new FormData(form));
});
Does not stop the event from bubbling. Parent listeners still run unless you also stop propagation.
e.defaultPrevented; // true after preventDefault in the same dispatch
stopPropagation — stop traveling the tree
button.addEventListener('click', (e) => {
e.stopPropagation();
// document-level click-outside handler will NOT see this click
});
Does not cancel default actions by itself. A click on <a> still navigates if you only stop propagation.
Both when you mean both
dropdownItem.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
select(item);
});
Capture vs bubble (why parent still runs)
parent.addEventListener('click', () => console.log('parent bubble'));
child.addEventListener('click', (e) => {
e.stopPropagation();
console.log('child');
});
// click child → child, parent does NOT run (bubble stopped)
parent.addEventListener('click', () => console.log('parent capture'), true);
// capture listener on parent runs BEFORE child — stopPropagation on child is too late for that capture
Order: capture (root → target) then bubble (target → root). stopPropagation prevents listeners on remaining nodes in the remaining phases.
stopImmediatePropagation
el.addEventListener('click', (e) => {
e.stopImmediatePropagation();
});
el.addEventListener('click', () => {
// never runs — same element, registered later
});
Stops other listeners on the same target too. Nuclear; use sparingly (libraries fighting each other).
Passive listeners and touch/wheel
window.addEventListener('touchmove', handler, { passive: true });
// handler calling preventDefault() is ignored (may warn)
// browser can scroll without waiting for JS
For custom pull-to-refresh or map pans that need preventDefault, register non-passive listeners (and accept jank risk if you run long tasks).
return false folklore
In inline handlers and jQuery, return false mixed preventDefault + stopPropagation. In addEventListener, return false does nothing useful. Call the methods explicitly.
Interview answer (out loud)
“preventDefault cancels the browser’s default action; stopPropagation stops the event from reaching other nodes. They’re independent. I know capture runs before target/bubble, stopImmediatePropagation blocks other listeners on the same node, and passive scroll/touch listeners can’t preventDefault.”
Keyboard defaults
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault(); // avoid form submit
submitForm();
}
});
Space on buttons, arrow keys in custom listboxes, and Escape in dialogs all interact with default browser behavior — preventDefault is the tool; also implement ARIA patterns.
Delegation still sees the event
list.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li) return;
// child called stopPropagation → this may not run
});
If a child stops propagation, parent delegation fails. Prefer not stopping unless necessary; filter in the parent instead.
React synthetic events note
React 17+ attaches root listeners; stopPropagation on DOM still works with caveats across nested roots. Prefer explicit props (onClick on parent with target checks) over fighting propagation in React trees.
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.