Using 'innerHTML' in JavaScript

zigzagoon1

zigzagoon1

Posted on February 5, 2023

Using 'innerHTML' in JavaScript

When working on a project recently, I stumbled on an issue where things weren't working as expected and I didn't understand why. As developers, we've all been there. Fortunately, with this particular issue I got lucky and it didn't take me very long to figure out what the problem was. I decided to share the details here to hopefully help somebody else facing similar issues!

The problem had to do with me using JavaScript to access and change an HTML elements' innerHTML, or more specifically, using += to update the contents of the innerHTML within a forEach block.

I had no idea that using += to add content to an elements' innerHTML could possibly cause issues, and as it turns out after doing some research, I didn't really understand that much about using the innerHTML property in general. Let's go over the basics of what innerHTML is now by comparing it to two other properties, innerText and textContent.

This example comes directly from W3 Schools website on their page about innerHTML, but I think it perfectly demonstrates each property so I'm going to recreate it here. To see the full example plus other information, you can visit W3Schools.

HTML:

<p id="myP"> This element has extra spacing    and contains <span>a span element</span>.</p>
Enter fullscreen mode Exit fullscreen mode

JavaScript:

let text = document.getElementById("myP").innerText;
Enter fullscreen mode Exit fullscreen mode

This example with innerText will return:

This element has extra spacing and contains a span element.

let text = document.getElementById("myP").textContent;
Enter fullscreen mode Exit fullscreen mode

This example with textContent will return:

This element has extra spacing and contains a span element.

let text = document.getElementById("myP").innerHTML;
Enter fullscreen mode Exit fullscreen mode

This example with innerHTML will return:

This element has extra spacing and contains <span>a span element</span>.

As you can see, an elements' innerHTML includes HTML tags for any children of the element, as well as any text that it or its children contains. This means that, when adding content to an elements' innerHTML, it is possible to include tags to create child elements, although it's recommended to use appendChild to add child elements rather than using innerHTML.

With innerHTML, it's even possible to include tags for a script, although W3Schools states than any script tags added to an element using their innerHTML will not be executed. Unfortunately, it's possible for bad characters to get around this using, for example, an img tag: they can make it so that their image will never load and provide an onerror attribute that can then include malicious code that will be executed by the browser when it inevitably produces an error when trying to load the image! This makes using an elements' innerHTML a security risk, so it is not advisable to add any user-generated content to your site by amending an elements' innerHTML property. For example, in a comment system, users would be able to write code to hack other users' private information if the developer were to add new user comments to their websites' by adding the comment contents to an elements' innerHTML! This is when using textContent or innerText is a better option. If you must use innerHTML for some reason, you should sanitize the content before using it. MDN describes a handy function to use for exactly this purpose: SetHTML

If you need to keep HTML markup that would be sanitized by the SetHTML function but still want the security of knowing there's no harmful content, there is also a helper library from DOMPurify that will remove any HTML markup that is not whitelisted.

So, what about using += with innerHTML?

As concerning as the security risk with using innerHTML is, it's time to get back to the reason I started researching innerHTML, and the reason for this post.

When appending to an elements' innerHTML, the whole element is reparsed. To show what this means, let's look at my code that was not working as expected:

...
const td = document.querySelector('#evo_into');
chain.forEach(el => {
  td.innerHTML += ', <a id="' + el.species['name'] + 
  '"href="user_search_bar">' + el.species['name'] + '</a>';
 const element = document.querySelector(`#${el.species['name']}`);
  element.addEventListener('click', function() {...})
}
Enter fullscreen mode Exit fullscreen mode

In this code, I was trying to add a click event listener for every item within the chain of evolutions (yes, it's a Pokémon project!). This is specifically for cases where one Pokémon can evolve into multiple different Pokémon. So, for each evolution, I was adding an a element with the text being equal to the name of the Pokémon, and the link just bringing the user back to the top of the page. I then attempted to add an event listener on each evolutions element that would load more information about that specific Pokémon to the DOM when the link is clicked. However, I was confused to find that none of the event listeners worked except the very last one! Why might this be? Well, because appending to the innerHTML reparses the entire content of the innerHTML string, each time the code in the forEach() block was run and I used += to append to the innerHTML, all the elements that I had created before that were essentially recreated, except, of course, the code to create their event listener was not re-executed. This is why only the last element in the evolution list had a working click event listener; there were no more additions to the innerHTML, and the element was not reparsed again, leaving one intact event listener.

To solve this, I simply created a string that I added to with each cycle of the forEach() block, and after that block I added the entire string to the innerHTML and created the event listeners after that. This is of course just one way I could have solved this problem; another solution I might have gone with would be to make use of a handy function, insertAdjacentHTML that would allow me to specify where in the element I wanted to insert my new HTML content, preserving the previous content as-is and therefore keeping any existing event listeners active.

As developers, we often come across errors or unexpected behavior in our code that can be frustrating to say the least! But it's important to remember that these mistakes are an opportunity to learn and understand more about our code. Without this experience, I may never have done research into using innerHTML, and in the future I might have misused it in a way that was dangerous to any users of my websites without realizing it! My mistake helped me grow as a developer, and I hope the experience can help other new developers out there as well. Thanks for reading!

💖 💪 🙅 🚩
zigzagoon1
zigzagoon1

Posted on February 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related