What you'll be able to do

By the end of this page you should be able to attach listeners with addEventListener, read the event object, call preventDefault when the default browser action gets in the way, and spot double-bound handlers.

Previous post: forms and user input.

Who this is for

  • People who used onclick="..." in HTML and want the JS-file style
  • Beginners whose button “fires twice” after a re-render
  • Anyone submitting forms or links that should not reload the page

You can skip this if listeners feel routine. Come back when duplicate handlers or default link behavior bites you.

addEventListener basics

const button = document.querySelector("#save");
button.addEventListener("click", function (event) {
  console.log("clicked", event.target);
});

Scenario A — separate HTML and behavior. Logic lives in <script> or a module.

Scenario B — multiple listeners on one element. addEventListener stacks them; old onclick = overwrites the previous one.

preventDefault

link.addEventListener("click", function (event) {
  event.preventDefault();
  // custom in-page navigation
});

Scenario A — <a href="#"> that should not jump.

Scenario B — form submit handled in JS (you saw this in the forms post).

Does not stop bubbling — that is the next posts.

Double-bound handlers

function onSave() { console.log("save"); }
button.addEventListener("click", onSave);
button.addEventListener("click", onSave); // fires twice per click

Scenario A — render function runs again and adds another listener without removing the old one.

Fix habits: bind once after DOM ready; or remove with removeEventListener (same function reference); or use delegation (post #47).

Named vs inline functions for removeEventListener

function handleClick() { /* ... */ }
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);

Anonymous functions cannot be removed unless you keep the reference — another reason named handlers help.

Common event types (starter set)

EventWhen
clickbuttons, links
inputtext fields as user types
changecommit on select/checkbox
submitform submit

Scenario A — live search box. input event.

Scenario B — dropdown picked. change event.

A tiny practice file

Save as events.html.

<!DOCTYPE html>
<html>
  <body>
    <a id="fake" href="https://example.com">Go (blocked)</a>
    <button id="once">Log once per setup</button>
    <pre id="out"></pre>
    <script>
      const out = document.getElementById("out");
      document.getElementById("fake").addEventListener("click", function (e) {
        e.preventDefault();
        out.textContent = "Default navigation prevented.";
      });
      function logClick() {
        out.textContent = (out.textContent || "") + "click\n";
      }
      const btn = document.getElementById("once");
      btn.addEventListener("click", logClick);
      // btn.addEventListener("click", logClick); // uncomment to see double fire
    </script>
  </body>
</html>

Mistakes I see a lot

1. Adding listeners inside a loop over the same parent without delegation.

2. Double add on every re-render.

3. Confusing preventDefault with stopPropagation.

4. Using click on disabled buttons — they do not fire; style alone is not enough for accessibility.

5. Forgetting { once: true } for one-shot setup when appropriate.

What to try before the next post

  1. preventDefault on a link.
  2. Add the same listener twice; observe double logs.
  3. Remove a named listener.
  4. Build events.html.

Next in this series: event bubbling and capturing — why a click on a button also reaches its parents.

Try this next outside the series

Browser JavaScript stops feeling isolated once you connect it to forms and component-oriented UI work.