When MutationObserver is set up multiple times, duplicate observers can be registered on the same element. While managing them with a global Set or Map is an option, writing an observed flag into dataset is lightweight and easy to port.
Code
const OBSERVE_ID_KEY = 'observeId'
const isElementObserved = <T extends HTMLElement = HTMLElement>( ele: T) => Object.hasOwn(ele.dataset, OBSERVE_ID_KEY)
const setObserverId = (id: string) => <T extends HTMLElement = HTMLElement>(ele: T): T => { ele.dataset[OBSERVE_ID_KEY] = id return ele }
export const observeElement = ( observeId: string, element: HTMLElement | string, observerCallback: MutationCallback, options: MutationObserverInit = { childList: true }) => { const observedElement = element instanceof HTMLElement ? element : document.querySelector<HTMLElement>(element) if (observedElement && !isElementObserved(observedElement)) { const observer = new MutationObserver(observerCallback) observer.observe(observedElement, options) setObserverId(observeId)(observedElement) return observer }}Usage
// Accepts both a selector string or an HTMLElementobserveElement( 'my-list-observer', '#item-list', (mutations) => { for (const mutation of mutations) { console.log('Change detected:', mutation.type) } })
// Calling again on the same element does not register a duplicate observerobserveElement('my-list-observer', '#item-list', callback)How It Works
Object.hasOwn(ele.dataset, OBSERVE_ID_KEY)checks for the presence of thedata-observe-idattribute- Only if unregistered, a
MutationObserveris created andobserve()is called - After observation starts, the flag is written via
ele.dataset[OBSERVE_ID_KEY] = id - On subsequent calls,
isElementObservedreturnstrueand nothing happens
Because dataset values are written directly to the HTML data-* attribute, they are visible in DevTools.
Benefits
- No global registry: State is stored on the element itself, eliminating the need to manage a separate
MaporSet - Highly portable: No framework dependencies; works in any environment
- Easy to debug: Attribute values can be inspected directly in DevTools
Caveats
When an element is removed from the DOM, its dataset information is also lost. If the same element is re-inserted, an observer will be registered again. Also, to stop an observer, you must call observer.disconnect() separately.
hsb.horse