Craftstack Academy
Web

Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

· Updated · 6 min read

You added a button and a few lines of JavaScript to make it do something. You click it, nothing happens, and the browser console says:

Console
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

The message sounds technical, but it says something simple: JavaScript didn't find your element. Here's how to read it, the mistakes that cause it almost every time, and a few related errors that look alike.

What the error means

Read it from the end:

  • (reading 'addEventListener'): your code tried to use addEventListener on something.
  • of null: that something was null, which means "nothing".
  • Cannot read properties: you can't call anything on nothing.

So the variable you called addEventListener on is null. Where did it come from? Almost always from a line like this, just above:

app.js
const button = document.querySelector("#color-btn");

document.querySelector gives back the first element that matches the selector, or null if nothing on the page matches. Then the next line fails:

app.js
button.addEventListener("click", () => {
  document.body.style.background = "lightyellow";
});

The error also stops your script at that line, so nothing written after it runs either.

Cause 1: a missing # in the selector

querySelector uses CSS selectors. An id needs a # in front, and a class needs a .:

index.html
<button id="color-btn">Change color</button>
<script>
  const button = document.querySelector("color-btn");
  button.addEventListener("click", () => {
    document.body.style.background = "lightyellow";
  });
</script>

Without the #, "color-btn" means "an element whose tag name is color-btn", like <color-btn>. There's no such tag on the page, so you get null.

The fix is one character:

js
const button = document.querySelector("#color-btn");

Or use getElementById, which takes the id without the #:

js
const button = document.getElementById("color-btn");

Check the spelling too: color-btn in the HTML and colour-btn in the JavaScript are different ids, and the result is the same error.

Cause 2: the script runs before the element exists

This version has a perfect selector and still fails:

index.html
<!DOCTYPE html>
<html>
<head>
  <script src="app.js"></script>
</head>
<body>
  <button id="color-btn">Change color</button>
</body>
</html>

The browser reads the page from top to bottom. It reaches the <script> in the <head> and runs app.js right away, before it has read the <body>. At that moment the button doesn't exist yet, so querySelector returns null.

You have three ways to fix it. Any one is enough:

  • Move the <script> to the end of the <body>, just before </body>.
  • Keep it in the <head> but add defer: <script src="app.js" defer></script>. The browser then runs it after the page has been read.
  • Wrap your code so it waits for the page:
app.js
document.addEventListener("DOMContentLoaded", () => {
  const button = document.querySelector("#color-btn");
  button.addEventListener("click", () => {
    document.body.style.background = "lightyellow";
  });
});

Cause 3: an id that's really a class

The selector has to use the same kind of name as the HTML. Here the button has a class, but the selector looks for an id:

index.html
<button class="color-btn">Change color</button>
<script>
  const button = document.querySelector("#color-btn");
  button.addEventListener("click", () => {
    document.body.style.background = "lightyellow";
  });
</script>

#color-btn only matches id="color-btn", so querySelector returns null and you get the same error. Match the selector to the HTML: ".color-btn" for a class, "#color-btn" for an id. And remember that getElementById only finds ids: it never finds an element by its class.

Cause 4: one script, several pages

Your site has a home page with the button and an About page without it, and both load the same app.js. On the home page everything works. On the About page, the console shows the error, and anything after that line in app.js doesn't run there either.

The script isn't wrong; the element simply isn't on every page. Only add the listener when the element exists:

app.js
const button = document.querySelector("#color-btn");
if (button) {
  button.addEventListener("click", () => {
    document.body.style.background = "lightyellow";
  });
}

Or, shorter, with the ?. operator, which skips the call when the value is null:

app.js
button?.addEventListener("click", () => {
  document.body.style.background = "lightyellow";
});

Use this only when the element is really optional. If it should be on the page, a silent ?. hides the real problem: fix the selector or the script's position instead.

Errors that look alike

addEventListener is not a function

getElementsByClassName and querySelectorAll don't return one element, but a list of all matches. A list has no addEventListener, so this fails with a different message:

app.js
const buttons = document.querySelectorAll(".btn");
buttons.addEventListener("click", () => {});
Console
Uncaught TypeError: buttons.addEventListener is not a function

Add the listener to each element in the list instead:

app.js
const buttons = document.querySelectorAll(".btn");
buttons.forEach(button => {
  button.addEventListener("click", () => {
    console.log("clicked", button.textContent);
  });
});

Cannot read properties of undefined (reading 'addEventListener')

undefined instead of null usually means you took an item from a list that was empty: document.getElementsByClassName("btn")[0] gives undefined when there's no element with that class. The causes and fixes are the same as above: check the class name and where the script runs.

Cannot set properties of null (setting 'textContent')

If you assign to the element instead of calling a method on it, like document.querySelector("#total").textContent = "5";, Chrome says Cannot set properties of null (setting 'textContent'). It's the same problem: the element wasn't found.

In Firefox and Safari

Other browsers word it differently. Firefox says something like button is null, and Safari something like null is not an object (evaluating 'button.addEventListener'). Whatever the wording, it means the same thing: the value before the dot is null.

How to find the culprit quickly

  1. Open the console (F12 in most browsers, then the Console tab) and click the file name and line number next to the error. It takes you to the line that failed.
  2. Look at the variable before the dot, here button, and find the line that created it.
  3. Log it: console.log(button);. If the console shows null, the selector found nothing.
  4. Compare the selector with the HTML: the # or ., the spelling, and whether the element is above or below your script.

The same pattern explains its cousins: (reading 'textContent'), (reading 'value') or (reading 'appendChild') all mean that the element before the dot is null.

We checked every example in this article in Chrome, which writes the messages exactly as shown. If you're just starting with JavaScript, our guide Which programming language should you learn first? explains where it fits next to HTML and CSS.

In our Web course, this error is one of the first things you meet when your JavaScript starts talking to the page, and the tutor helps you read the console instead of guessing.

Python

Why does 0.1 + 0.2 not equal 0.3 in Python?

Why Python prints 0.30000000000000004 for 0.1 + 0.2, why 0.1 + 0.2 == 0.3 is False, and the three fixes: round for display, math.isclose() instead of ==, and Decimal for money. With code you can run.

· 4 min read

Learn by writing the code yourself.

Twenty minutes with the tutor, right in your browser. No card, nothing to install.

Start the free trial
A session: the tutor's chat on the left, the code editor with a Java program and its output on the right