/ JAVASCRIPT FUNDAMENTALS /
Understanding JavaScript Event Listeners
Making a web page react to the user.
A web page made with HTML and CSS can display information, but JavaScript is what lets that page respond to what a user does.
A user might click a button, type into an input, submit a form, press a key, or move through a page. JavaScript calls these actions events.
An event listener tells JavaScript:
"When this particular thing happens, run this code."
That simple idea powers a huge amount of what we interact with on the web. Menus, forms, modals, search boxes, shopping carts, dashboards, and countless other interfaces depend on events.
Start with the problem
Imagine we have a button:
HTML
<button id="helloButton">Say Hello</button>The button exists, but clicking it does nothing. HTML created the element. Now JavaScript needs to define what happens when someone interacts with it.
JavaScript
const button = document.querySelector("#helloButton");button.addEventListener("click", function () {
console.log("Hello!");
});Button
What are we listening to?
"click"
What are we listening for?
Function
What should happen?
ELEMENT → EVENT → RESPONSE
Let's make something happen on the page
HTML
<button id="helloButton">Say Hello</button>
<p id="message"></p>JavaScript
const button = document.querySelector("#helloButton");
const message = document.querySelector("#message");
button.addEventListener("click", function () {
message.textContent = "Hello!";
});Live example
Waiting for a click...
Go ahead — click it. That's a live version of the code above, actually running.
User clicks button
JavaScript detects event
Function runs
Interface changes
That's the beginning of event-driven programming: code that waits, then reacts.
Separate the "what" from the "when"
const button = document.querySelector("#helloButton");
const message = document.querySelector("#message");
function sayHello() {
message.textContent = "Hello!";
}
button.addEventListener("click", sayHello);The function defines what should happen. The event listener defines when it should happen.
What
function sayHello() { ... }
When
button.addEventListener("click", sayHello);
Splitting these apart doesn't matter much for one button. It matters a lot once an application has dozens of them.
What actually happened?
button.addEventListener("click", function (event) {
console.log(event);
});button.addEventListener("click", function (event) {
console.log(event.target);
});JavaScript gives us information about the interaction through the event object. event.target tells us which element actually triggered it.
Click one of the three buttons below.
Live example
Click one of the buttons above.
Let's build something
HTML
<button id="counterButton">Clicked 0 times</button>JavaScript
const button = document.querySelector("#counterButton");
let count = 0;
button.addEventListener("click", function () {
count++;
button.textContent = `Clicked ${count} times`;
});Live example
State
let count = 0;
Event
click
Update state
count++;
Update interface
button.textContent = ...
This pattern appears everywhere in front-end development:
Something happens → data changes → interface updates.
React and other frameworks make this process more structured, but understanding the underlying idea first makes those abstractions easier to understand.
/ DEBUGGING /
Breaking the code is part of learning it
HTML
<button id="helloButton">Say Hello</button>Broken JavaScript
const button = document.querySelector("#heloButton");
button.addEventListener("click", function () {
console.log("Hello!");
});In the JavaScript
#heloButton
The actual id, in the HTML
#helloButton
Why doesn't our event listener work?
console.log(button);Result
null
The problem isn't actually the event listener. The problem happened before the listener was ever attached — JavaScript couldn't find the element in the first place.
What did I expect?
What actually happened?
Where do those two things become different?
Test one assumption at a time.
Passing a function vs. calling a function
function sayHello() {
console.log("Hello!");
}Incorrect
button.addEventListener("click", sayHello());Correct
button.addEventListener("click", sayHello);sayHello() runs the function immediately. sayHello passes the function itself to the event listener, so JavaScript can run it later, when the click actually happens.
sayHello()
Run it now
sayHello
Use this function later
Clicks are only the beginning
HTML
<input id="nameInput" type="text" placeholder="Type your name">
<p id="preview"></p>JavaScript
const input = document.querySelector("#nameInput");
const preview = document.querySelector("#preview");
input.addEventListener("input", function () {
preview.textContent = input.value;
});Live example
Whatever you type shows up here, live.
The event changed from "click" to "input", but the mental model stayed exactly the same:
ELEMENT → EVENT → RESPONSE
Sometimes we don't want the browser's default behavior
HTML
<form id="signupForm">
<input id="email" type="email">
<button type="submit">Sign Up</button>
</form>JavaScript
const form = document.querySelector("#signupForm");
form.addEventListener("submit", function (event) {
event.preventDefault();
console.log("Form submitted");
});Browsers already know how forms normally behave — submit one, and the page reloads. event.preventDefault() tells the browser: "Don't do the normal thing yet. I'm going to handle this with JavaScript."
That's what makes validation, sending data to an API, and updating the interface in place possible.
/ NEXT LEVEL /
One listener can sometimes handle many elements
HTML
<ul id="todoList">
<li>
Learn JavaScript
<button class="deleteButton">Delete</button>
</li>
<li>
Learn React
<button class="deleteButton">Delete</button>
</li>
<li>
Build a project
<button class="deleteButton">Delete</button>
</li>
</ul>JavaScript
const list = document.querySelector("#todoList");
list.addEventListener("click", function (event) {
if (event.target.matches(".deleteButton")) {
event.target.closest("li").remove();
}
});A click on a button inside the list "bubbles up" to the list itself, and the listener there can check event.target to figure out which button actually got clicked. Instead of creating a listener for every child element, we listen once, at the parent, and let the event tell us which child caused it.
Try it — delete a few items.
Live example
- Learn JavaScript
- Learn React
- Build a project
The bigger idea behind event listeners
Something happens.
Our application detects it.
Our code decides how to respond.
Dropdown
click → open menu
Search
input → update results
Form
submit → validate → send data
Keyboard shortcut
keydown → perform action
Modal
click → change interface state
Frameworks like React express these same ideas a little differently:
<button onClick={handleClick}>
Say Hello
</button>The syntax changes, but the underlying event-driven mental model is still there. Understanding the browser first makes the abstraction easier to understand later.
/ CHALLENGE /
Your turn
Starting HTML
<button id="button">Click me</button>
<p id="message">You haven't clicked yet.</p>Make the text update every time the button is clicked:
Clicked 1 time → Clicked 2 times → Clicked 3 times → ...
Extension: add a Reset button that returns the counter to zero.
Before you look at hints, think through:
- What information needs to change?
- What event are we listening for?
- What element needs to update?
Try your solution here
You haven't clicked yet.
How I teach
Make the why clear. Learn by building. Build confidence through debugging.
I want students to understand more than the code that solves one problem. I want them to develop a process they can use when they encounter the next one.