About the error
The error shown says that the classList property of "null could not be found".
Why the variable is set to "null"?
on the line:
const id = element.getAttribute('href').replace('#', '');
you try to redeem the value of the attribute "href" from the anchor that was clicked, and on the next line:
const article = document.getElementById(id);
you pass the received value as parameter of the getElementById médoto which for some reason returns null, indicating that there is no element in the DOM with that id. The possible causes for this may be:
- typo error - you may have typed the same words with high/low box letters on your link, or some typo and on your element that should be rescued by getElementById the value differs;
- missing value in any of the references - you may have forgotten to put the id on the anchor or element to be rescued by getElementById;
Solution
Just check if the values are correct in both parts.
Improvements
I would like to suggest some improvements to your code that can help in performance and avoid some possible bugs.
Avoid performing the same tasks in vain
In the function performed at each click data is stored and rescued from the gift, but this should only happen at each click of a "non-active" reference, for example:
- Imagine the anchors "A", "B" and "C" and the anchor "A" is active;
- The function triggered by the click should only perform the proper tasks if the click comes from the anchors "B" or "C" at this time, because "A" is already active and consequently its reference also.
Example:
const activeState = {
previous: 0,
current: 0,
};
function updateActiveState(current) {
activeState.previous = activeState.current;
activeState.current = current;
}
function onLinkClick(e) {
e.preventDefault();
const target = event.target;
const eIndex = parseInt(target.getAttribute('data-index'));
if(activeState.current !== eIndex) {
// do something
}
}
In this example I created an object to serve as "state" to know which element is currently active and which element was previously clicked.
Benefit: centralization of data decrease logic to add and/or remove classes.
Cache the elements to be manipulated
In its function executed every click on the line:
const article = document.getElementById(id);
DOM is required to rescue the element by its id which requires a certain use of browser memory.
To improve this, I suggest creating a cache in the previous scope of the function and using it to rescue these elements whenever you need it instead of going to the DOM again. For example:
// Resgata do DOM apenas uma vez o elemento que contém os artigos a serem manipulados
const articlesSection = document.querySelector('.articles');
// Resgata do Objeto em cache todos os elementos com a classe "article"
const articles = articlesSection.querySelectorAll('.article');
// Resgata do Objeto em cache o elemento com o id especificado
const article1 = articlesSection.querySelector('#article1');
const article2 = articlesSection.getElementById('#article2');
Benefits: performance
Dry the example with the improvements applied
const links = document.querySelectorAll('nav .link');
const articlesSection = document.querySelector('.articles');
const articles = articlesSection.querySelectorAll('.article');
const activeState = {
previous: 0,
current: 0,
};
function updateActiveState(current) {
activeState.previous = activeState.current;
activeState.current = current;
}
function onLinkClick(e) {
e.preventDefault();
const target = event.target;
const eIndex = parseInt(target.getAttribute('data-index'));
if(activeState.current !== eIndex) {
updateActiveState(eIndex);
const currentActive = activeState.current;
const previousActive = activeState.previous;
const currentRef = target.getAttribute('data-ref');
const previousRef = links[previousActive].getAttribute('data-ref');
const currentArticle = articlesSection.querySelector("#" + currentRef);
const previousArticle = articlesSection.querySelector("#" + previousRef);
const activeClass = 'active';
target.classList.add(activeClass);
links[previousActive].classList.remove(activeClass);
currentArticle.classList.add(activeClass);
previousArticle.classList.remove(activeClass);
}
}
links.forEach((link, idx) => {
link.classList.add(idx === 0 ? 'active' : null);
link.setAttribute('data-index', idx);
link.addEventListener('click', onLinkClick);
});
articles.forEach((article, idx) => {
article.classList.add(idx === 0 ? 'active' : null);
article.setAttribute('data-index', idx);
});
html, body {
margin: 0;
padding: 0;
height: 100%;
}
.container {
display: flex;
flex-direction: row;
align-items: start;
height: 100%;
}
nav, .articles {
box-sizing: border-box;
height: 100%;
}
nav {
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.7);
min-width: 150px;
}
nav .link, nav .link:visited {
color: #cccccc;
padding: 0.5em 1em;
text-decoration: none;
}
nav .link:not(:nth-child(1)), nav .link:visited {
border-top: 1px dotted rgba(255, 255, 255, 0.2)
}
nav .link:hover, nav .link:visited:hover, nav .link.active {
color: #ffffff;
}
.articles {
padding: 1em;
background: rgba(0, 0, 0, 0.03);
}
.articles .article:not(:nth-child(1)) {
padding-top: 10px;
}
.articles .article:not(.active) {
opacity: 0.3;
}
.article p {
margin: 0;
}
<div class="container">
<nav>
<a href="#" class="link" data-ref="one">Lorem.</a>
<a href="#" class="link" data-ref="two">Ullam?</a>
<a href="#" class="link" data-ref="three">Amet.</a>
</nav>
<section class="articles">
<div class="article" id="one">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Vero, laboriosam.</p>
</div>
<div class="article" id="two">
<p>Ab explicabo voluptatibus corporis quas aliquid officiis ratione accusamus unde.</p>
</div>
<div class="article" id="three">
<p>Exercitationem assumenda quis quidem, est sapiente nihil provident at sunt!</p>
</div>
</section>
</div>
Which line of error?
– Sam
Looking at the code, it seems to me that you are trying to grab the id of a clicked <a> and add the . class active in a <article> with the same id. This is not going to work anyway. Put the HTML code tb so we can see.
– Sam
That’s the idea, the problem is I need to do it!
– Randys Machado