Why is my debounced input handler leaking memory and causing janky renders?

Ellen1979
Sun, 26 Apr 2026 21:00:31 +0000

yes — a plain Map can keep the input node alive, so the node, the handler closure, and whatever the closure captures can hang around longer than they should.

The timeout needs clearing on teardown too. If you only remove the event listener but leave handler.t pending, you can still get a late setQuery() after the component is gone, which is where the jank starts feeling haunted.

The remount issue sounds more like you’re unwiring the wrong element reference. Old input gets replaced, you call unwireSearch() on the new node, and the old listener is still sitting there on the dead DOM.

I’d keep the registry weak and clear the timer before removing the listener:

const listeners = new WeakMap();

export function wireSearch(input, store) {
  if (listeners.has(input)) return;

  const handler = () => {
    clearTimeout(handler.t);
    handler.t = setTimeout(() => store.setQuery(input.value), 80);
  };

  input.addEventListener('input', handler);
  listeners.set(input, handler);
}

export function unwireSearch(input) {
  const handler = listeners.get(input);
  if (!handler) return;

  clearTimeout(handler.t);
  input.removeEventListener('input', handler);
  listeners.delete(input);
}

If this is in a framework, I’d check the teardown path first. Half the time the “memory leak” is just a stale node reference and a timer that never got killed.

Read full topic