Spot the bug - #111: Drag Handle

Drag handle works once then completely stops responding, help

const handle = document.querySelector('.drag-handle');
const box = document.querySelector('.box');

function onDrag(e) {
  box.style.left = e.clientX + 'px';
  box.style.top = e.clientY + 'px';
}

handle.addEventListener('mousedown', () => {
  document.addEventListener('mousemove', onDrag);
  document.addEventListener('mouseup', () => {
    document.removeEventListener('mousemove', onDrag);
  });
});

Reply with what is broken and how you would fix it.

New mouseup listener gets added every single mousedown, they never get cleaned up so they stack up and start fighting each other. Only the first one actually manages to remove the mousemove listener since it’s referencing the same onDrag function reference, the rest are just dead listeners doing nothing. Pull the mouseup handler out to a named function and add/remove it the same way you do with onDrag, that’s the actual fix here. Rebinding closures inside a mousedown handler is asking for this exact bug.

Spot the Bug answer: A new mouseup listener (with a new anonymous function reference) is added on every mousedown, so removeEventListener never actually matches a previously added listener, but more importantly listeners pile up and only the closures created during the very first drag properly reference onDrag, leaving stale duplicate mouseup handlers that no longer clean up correctly after the first drag.

The fix:

Define the mouseup handler as a named function once outside mousedown, and use document.addEventListener('mouseup', onDragEnd) / removeEventListener('mouseup', onDragEnd) instead of creating a new anonymous function inside mousedown each time.

Why:
Each mousedown registers a brand new anonymous mouseup callback, so the handlers accumulate instead of being properly removed, and because addEventListener/removeEventListener require the same function reference, the drag state becomes inconsistent after the first use. This causes the handle to stop responding correctly on subsequent drags.

First-answer leaderboard

  1. @kirupa - 3 (firsts) :trophy:
  2. @emmawalter5 - 1 (first)