Reactive Patterns — Observer & Pub/Sub in JavaScript
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). ESLint can flag global window variables and direct DOM querying patterns (per detection_hints), but detecting missing event listener cleanup or inefficient event-based patterns requires static analysis beyond linting or runtime profiling — not caught automatically by default tooling.
Closest to 'simple parameterised fix' (e3). The quick_fix is straightforward: replace global state/direct DOM queries with CustomEvent + dispatchEvent. This typically affects one or two components' communication layer, not cross-cutting refactor. Memory leak fixes (removing listeners) are localized changes per component.
Closest to 'persistent productivity tax' (b5). Reactive patterns apply across web contexts and affect how all future component communication is architected. Choice of event-based vs. global state shapes how developers think about component decoupling; it influences testing, state management, and coordination logic across many feature teams — not just one component, but a persistent pattern.
Closest to 'serious trap' (t7). The misconception field directly names it: developers assume reactive programming *requires* React/Vue/RxJS, so they either over-engineer with RxJS or fall back to global variables. CustomEvent's subtle API (event.detail for payload, listener cleanup gotchas, memory leak risk on SPA-like page mutations) contradicts how similar patterns work in framework-land, creating cognitive friction for developers familiar with framework reactivity.
Also Known As
TL;DR
Explanation
Patterns: EventEmitter (Node.js), CustomEvent + dispatchEvent (browser), and simple pub/sub objects. CustomEvent lets PHP-rendered DOM elements communicate: a checkout component fires 'cart:updated', a header badge listens. No framework needed. EventTarget interface can be extended. This pattern works well in 'islands architecture' where PHP renders HTML and small JS components communicate via events rather than shared state.
Common Misconception
Why It Matters
Common Mistakes
- Not removing event listeners on element removal — memory leak
- Using global variables to share state instead of events
- Over-engineering with RxJS when CustomEvent suffices
Code Examples
// Global variable coupling — fragile:
window.cartCount = 3;
document.querySelector('#badge').textContent = window.cartCount;
// Custom event — decoupled:
// Cart component dispatches:
document.dispatchEvent(new CustomEvent('cart:updated', {
detail: { count: 3, total: 29.99 }
}));
// Badge component listens:
document.addEventListener('cart:updated', (e) => {
badge.textContent = e.detail.count;
});
// Simple pub/sub for Node/vanilla:
const bus = {
on: (e, fn) => document.addEventListener(e, fn),
off: (e, fn) => document.removeEventListener(e, fn),
emit: (e, d) => document.dispatchEvent(new CustomEvent(e, { detail: d })),
};