233

I have to use vanilla JavaScript for a project. I have a few functions, one of which is a button that opens a menu. It works on pages where the target id exists, but causes an error on pages where the id doesn't exist. On those pages where the function cannot find the id, I receive a "Cannot read property 'addEventListener' of null " error and none of my other functions work.

Below is the code for the button that opens the menu.

function swapper() {
toggleClass(document.getElementById('overlay'), 'open');
}

var el = document.getElementById('overlayBtn');
el.addEventListener('click', swapper, false);

var text = document.getElementById('overlayBtn');
text.onclick = function(){
this.innerHTML = (this.innerHTML === "Menu") ? "Close" : "Menu";
return false;
};

How do I deal with this? I probably need to all wrap this code in another function or use an if/else statement so that it only searches for the id on specific pages, but not sure exactly.

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
morocklo
  • 2,500
  • 2
  • 14
  • 12
  • 2
    can you show the html code. it seems that can not find element with id 'overlayBtn' – BlaShadow Sep 29 '14 at 19:14
  • 3
    On those pages where the function cannot find the id, I receive a "Cannot read property 'addEventListener' of null " error and none of my other functions work. I think the answer was pretty much in the question. You couldn't find the element, so you can't add an event listener to it... – Etai Sep 29 '14 at 19:16
  • 3
    It can simply happen if you have used `class` in your html instead of `id` and you calling for a `getElementById` in your scripts. – Deke Jun 25 '16 at 19:50
  • 19
    Just faced similar issue where `addEventListener could be null`. Moving `` after `

    ` tag seem to solve this issue.

    – Malakai Aug 30 '20 at 01:01
  • @Reborn I was getting this error for so long. This solved it. – r3za May 25 '21 at 10:35
  • Best Approach is moving after – Aryan Aug 01 '21 at 08:36
  • When you use in tag, document elements have not loaded yet. Moving before tag. – Darwin Jan 07 '23 at 06:07

27 Answers27

351

I think the easiest approach would be to just check that el is not null before adding an event listener:

const el = document.getElementById('overlayBtn');
if (el) {
  el.addEventListener('click', swapper, false);
}

You can also wait to run your code until DOMContentLoaded has completed:

window.addEventListener("DOMContentLoaded", (event) => {
    const el = document.getElementById('overlayBtn');
    if (el) {
      el.addEventListener('click', swapper, false);
    }
});
Rob M.
  • 35,491
  • 6
  • 51
  • 50
  • 2
    awesome. that did the trick. I also moved the onclick function into that if statement. I posted the final code below. – morocklo Sep 29 '14 at 19:26
  • 4
    it will be `null` before react component did mount! –  Sep 12 '17 at 08:36
  • Thanks man :D Exactly what I was looking for....I've got multiple listeners in my app, and the listeners are spread out in different views. If the element is present on the page, it was mucking up my JS – VegaStudios Jun 11 '19 at 16:05
  • I have combined this method with the following https://stackoverflow.com/a/1760268/5923833 to resolve the issue with this error. In my case the trigger for the error was hitting enter or reload for the page and then changing the tab to another page. Now, with your code and the other solution I was able to prevent the error on page load and then add the event listener when the user comes back to the tab. – bachree Mar 04 '21 at 19:20
  • 2
    Insted move the after – Aryan Aug 01 '21 at 08:37
  • I had a similar problem with dynamic forms generated with php, which meant under certain conditions the elements were not there throwing an error in the js. This fixed it for me. Thanks! –  Jan 11 '23 at 11:15
211

It seems that document.getElementById('overlayBtn'); is returning null because it executes before the DOM fully loads.

If you put this line of code under

window.onload=function(){
  -- put your code here
}

then it will run without issue.

Example:

window.onload=function(){
    var mb = document.getElementById("b");
    mb.addEventListener("click", handler);
    mb.addEventListener("click", handler2);
}


function handler() {
    $("p").html("<br>" + $("p").text() + "<br>You clicked me-1!<br>");
}

function handler2() {
    $("p").html("<br>" + $("p").text() + "<br>You clicked me-2!<br>");
}
Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
Dilip Agheda
  • 2,447
  • 1
  • 11
  • 15
59

I faced a similar situation. This is probably because the script is executed before the page loads. By placing the script at the bottom of the page, I circumvented the problem.

sridhar
  • 1,321
  • 5
  • 17
  • 26
42

script is loading before body, keep script after body

Sagar M
  • 1,168
  • 13
  • 10
22

I was getting the same error, but performing a null check did not seem to help.

The solution I found was to wrap my function inside an event listener for the whole document to check when the DOM finished loading.

document.addEventListener('DOMContentLoaded', function () {
    el.addEventListener('click', swapper, false);
});

I think this is because I am using a framework (Angular) that is changing my HTML classes and ID's dynamically.

MattSidor
  • 2,599
  • 4
  • 21
  • 32
19

Put script at the end of body tag.

<html>
    <body>
        .........
        <script src="main.js"></script>
    </body>
</html>
Zoe
  • 27,060
  • 21
  • 118
  • 148
matak8s
  • 497
  • 4
  • 7
12

It's just bcz your JS gets loaded before the HTML part and so it can't find that element. Just put your whole JS code inside a function which will be called when the window gets loaded.

You can also put your Javascript code below the html.

Ajit Kumar
  • 590
  • 1
  • 8
  • 17
9

Your script is probably placed in the head tag of HTML and is loading before the DOM loads. There can be two solutions to this:

  1. Place the script tag just before the closing body tag (</body>).
  2. Add the async keyword to your script tag.
<script async src="script.js"></script>

With async, the file gets downloaded asynchronously and then executed as soon as it's downloaded.

I prefer 2nd option better. Read more about async and defer here

Gaurav
  • 857
  • 3
  • 21
  • 29
8

Use this way if your js code is the external file:

<script src="filename.js" defer></script>

Either if your code is the internal file, use DOMContentLoaded

Two ways above will make sure that DOM fully loads before executing js code!

Kev
  • 315
  • 1
  • 4
  • 11
7

This question already has an answer but what i like to do for those simple conditions is using the logical AND (&&) operator :

var el = document.getElementById('overlayBtn');

el && el.addEventListener('click', swapper, false);

More readable and concise in my opinion.

Manel
  • 1,616
  • 19
  • 42
5

Thanks to @Rob M. for his help. This is what the final block of code looked like:

function swapper() {
  toggleClass(document.getElementById('overlay'), 'open');
}

var el = document.getElementById('overlayBtn');
if (el){
  el.addEventListener('click', swapper, false);

  var text = document.getElementById('overlayBtn');
  text.onclick = function(){
    this.innerHTML = (this.innerHTML === "Menu") ? "Close" : "Menu";
    return false;
  };
}
Max
  • 1,054
  • 1
  • 12
  • 20
morocklo
  • 2,500
  • 2
  • 14
  • 12
5

You could add the attribute defer to your script tag. The reason why you're seeing that error is because the script is running before the DOM is loading.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/styles.css">
    <script src="scripts/main.js" defer="defer"></script>
</head>

Defer is a boolean value that indicates to run script once the DOM has been parsed

5

I think the most accessible approach would be to add a '?' question mark before el:

 var el = document.getElementById('overlayBtn');
 el?.addEventListener('click', swapper, false);
abel
  • 101
  • 3
  • 2
3

I encountered the same problem and checked for null but it did not help. Because the script was loading before page load. So just by placing the script before the end body tag solved the problem.

Mahmud
  • 369
  • 4
  • 13
3

I had the same problem, but my id was present. So I tried adding "window.onload = init;" Then I wrapped my original JS code with an init function (call it what you want). This worked, so at least in my case, I was adding an event listener before my document loaded. This could be what you are experiencing as well.

ultrageek
  • 637
  • 1
  • 6
  • 13
2

This is because , people have habit of defining their javascript file in the head itself , So when you do something in html tags than your javscript file is executed first than your html file so the value returned is NULL.

Solution: Define your Javascript file at the end of your bodytag`;

Manishyadav
  • 1,361
  • 1
  • 8
  • 26
1

As others have said problem is that script is executed before the page (and in particular the target element) is loaded.

But I don't like the solution of reordering the content.

Preferred solution is to put an event handler on page onload event and set the Listener there. That will ensure the page and the target element is loaded before the assignment is executed. eg

    <script>
    function onLoadFunct(){
            // set Listener here, also using suggested test for null
    }
    ....
    </script>

    <body onload="onLoadFunct()" ....>
    .....
pjm
  • 295
  • 2
  • 10
1

I've a collection of quotes along with names. I'm using update button to update the last quote associated with a specific name but on clicking update button it's not updating. I'm including code below for server.js file and external js file (main.js).

main.js (external js)

var update = document.getElementById('update');
if (update){
update.addEventListener('click', function () {

  fetch('quotes', {
  method: 'put',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    'name': 'Muskan',
    'quote': 'I find your lack of faith disturbing.'
  })
})var update = document.getElementById('update');
if (update){
update.addEventListener('click', function () {

  fetch('quotes', {
  method: 'put',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    'name': 'Muskan',
    'quote': 'I find your lack of faith disturbing.'
  })
})
.then(res =>{
    if(res.ok) return res.json()
})
.then(data =>{
    console.log(data);
    window.location.reload(true);
})
})
}

server.js file

app.put('/quotes', (req, res) => {
  db.collection('quotations').findOneAndUpdate({name: 'Vikas'},{
    $set:{
        name: req.body.name,
        quote: req.body.quote
    }
  },{
    sort: {_id: -1},
    upsert: true
  },(err, result) =>{
    if (err) return res.send(err);
    res.send(result);
  })

})
Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
1

I simply added 'async' to my script tag, which seems to have fixed the issue. I'm not sure why, if someone can explain, but it worked for me. My guess is that the page isn't waiting for the script to load, so the page loads at the same time as the JavaScript.

Async/Await enables us to write asynchronous code in a synchronous fashion. it’s just syntactic sugar using generators and yield statements to “pause” execution, giving us the ability to assign it to a variable!

Here's reference link- https://medium.com/siliconwat/how-javascript-async-await-works-3cab4b7d21da

Andy Smith
  • 417
  • 4
  • 9
1

I got this issue recently, and I found I was providing a wrong classname in getElementById and querySelector. So, I will suggest to also cross-check your class and id names in your script and html file.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Raunak Hajela
  • 103
  • 2
  • 10
1

I think the easiest approach is to use defer. Don't know why no one posted this answer-

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="style.js" defer></script>
</head>
Pranjal
  • 21
  • 4
0

I faced this problem when I tried to get an element by Id which was a class. Check and confirm that the your getting the element by the correct attribute e.g id or class. else you will have this error

0

This error also happens when the "element is not present in the DOM" or when the id or classname doesn't exist but you are asking for it in the scripts file. Hence, you are calling an event on null.

Deke
  • 4,451
  • 4
  • 44
  • 65
0

using optional chaining operator (?.) will return an undefined on pages the element doesn't exist instead of throwing an error. example;

el?.addEventListener('click', swapper, false);
Tijani Eneye
  • 117
  • 2
  • 9
0

For me the issue was page loading after a refactor

document.addEventListener('DOMContentLoaded', function() {
    // Your Code Here
});
Brendan Jackson
  • 305
  • 1
  • 3
  • 13
-1

This is because the element hadn't been loaded at the time when the bundle js was being executed.

I'd move the <script src="sample.js" type="text/javascript"></script> to the very bottom of the index.html file. This way you can ensure script is executed after all the html elements have been parsed and rendered .

JMP
  • 4,417
  • 17
  • 30
  • 41
Xcode
  • 129
  • 1
  • 11
  • 1
    Incorrect. This is old school thinking. Loading at the end delays the loading, as you stated, but it **does not ensure the DOM is fully drawn**. I've had older pages that used this hack fail to run because the DOM draw was slower than the load. [This answer](https://stackoverflow.com/a/30063375/2370483) **ensures** the DOM is drawn before execution. – Machavity Dec 30 '17 at 16:29
  • But @Machavity, in that answer also, the function is waiting for the DOM to load completely. So what is the difference here. Both answers are right?? Could you please explain. – Vyshak Puthusseri Mar 27 '21 at 13:50
  • 1
    @VyshakPuthusseri They're not quite the same thing. Your browser has to load content, then draw the content (DOM) out. In most cases the browser will finish in the order loaded, but there are times when that's not true, in which case your JS breaks trying to reference unrendered objects. [DOMContentLoaded](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) is an event that explicitly lets you wait for the browser to finish drawing the DOM first, rather than crossing your fingers and hoping for the best. – Machavity Mar 27 '21 at 14:44
  • Ok. Understood my mistake. @Machavity Thanks for that information. – Vyshak Puthusseri Mar 28 '21 at 09:15
-4

Add all event listeners when a window loads.Works like a charm no matter where you put script tags.

window.addEventListener("load", startup);

function startup() {

  document.getElementById("el").addEventListener("click", myFunc);
  document.getElementById("el2").addEventListener("input", myFunc);

}

myFunc(){}
Popovkov57
  • 179
  • 16
  • The drawing of the page can sometimes take longer than the loading of the script does, so the position isn't that important in all cases. Adding a listener is the preferred way, but `load` is also deprecated for the same reason. [DOMContentLoaded](https://stackoverflow.com/questions/2414750/difference-between-domcontentloaded-and-load-events) is the preferred way, since it fires only after the DOM is fully drawn. – Machavity Dec 30 '17 at 16:35
  • Thanks I will try. I got my answer from Mozilla network. I thought it was a trustful source. – Natalie Jimenez Dec 30 '17 at 16:50
  • This code does not respond to the question. If elements are not found (el, el2) then error will show in console. – Manel Apr 17 '21 at 13:54