2

I am trying to add 'active' class on my link. It is saying

Uncaught TypeError: document.querySelectorAll(...).each is not a function

Here is my code:

const current = window.location.href;
document.querySelectorAll("#nav-tab a").each(function(){
    const $this = this;
    
    if($this.attr('href').indexOf(current) !== -1){
        $this.classList.add("active");
    }
});

Can you help me? Or is there a better way to add a class name based on the current URL? Thank you so much!

7
  • 4
    It is .forEach() not .each() (provided that your browser supports .forEach()) Commented Jul 6, 2022 at 8:41
  • 1
    Try with forEach document.querySelectorAll("#nav-tab a").forEach(function(){ Commented Jul 6, 2022 at 8:42
  • Hey @NickParsons and @angel.bonev! Thanks guys that worked! Now I have a new error that says: Uncaught TypeError: Cannot read properties of undefined (reading 'attr') hmmm Commented Jul 6, 2022 at 8:47
  • 2
    @ddd you're confusing jQuery methods with regular DOM methods. You want to us .getAttribute(), not .attr() (even using .href should work) Commented Jul 6, 2022 at 8:48
  • 1
    @ddd oh yep, this inside of your function also isn't going to be the lement. You want to access the element in your function using the argument of the function function(elem) {, then call your methods on elem instead of $this Commented Jul 6, 2022 at 8:56

2 Answers 2

2

You have a few issues with your code. Your mainly confusing jQuery methods with regular native browser methods/conventions:

  1. You need to use .forEach() and not .each(). The .forEach() method is a method on the NodeList that querySelectorAll() returns.

  2. .attr() is not a valid method. To get an element's attribute you can use .getAttribute(). We can use .href here instead to get the href. Note that getAttribute("href") will retrieve the URL as it is in your mark-up, whereas .href will retrieve the full, eg, if you had href="/foo/bar", .href will give https://example.com/foo/bar, whereas .getAttribute() will return just /foo/bar.

  3. Use the element parameter of the function instead of this. When you use .forEach() you're iterating over the elements in your NodeList (ie: the elements you selected), so you can access each using the first parameter of the forEach callback. The this value in the browser (if not in strict mode) will default to window, so it won't be the element like you're expecting it to be:

const current = window.location.href;
document.querySelectorAll("#nav-tab a").forEach(function(elem){ 
    if(elem.href.includes(current)){
      elem.classList.add("active");
    }
});

I've also changed .indexOf(...) !== -1 to .includes(), which is a more modern way to check if a string contains another value.


I will point out that you can make your query selector more advanced, which will limit the number of elements you iterate:

const current = window.location.href;
document.querySelectorAll(`#nav-tab a[href*="${current}"]`).forEach(elem => { 
  elem.classList.add("active");
});

This uses the attribute selector a[href*=...] to select the a elements that have a href that contains the text in stored in current.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for helping and for explaining, @Nick Parsons!!
1

I think that you are mistaken jQuery and vanilla javascript

$.each is a jQuery function in you case you can use .forEach

$.attr is a jQuery function in you case you can use .getAttribute

const current = "#test-3";//window.location.href;
document.querySelectorAll("#nav-tab a").forEach((elem) => {
    if (elem.getAttribute('href').indexOf(current) !== -1) {
        elem.classList.add("active");
    }
});
.active { color:red}
<div id="nav-tab">
    <a href="#test-1">Test 1</a>
    <a href="#test-2">Test 2</a>
    <a href="#test-3">Test 3</a>
    <a href="#test-4">Test 4</a>
</div>

2 Comments

Thank you, @angel.bonev!! Right, I confused jQuery with Vanilla JS!
@ddd You're welcome. It happens to me in my early programming years. I know the feeling. Cheers

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.