If you’ve ever dynamically added HTML in JavaScript, you’ve probably used innerHTML
like this:
document.getElementById("container").innerHTML += "<div>New item</div>";
But did you know this approach has major downsides?
- ❌ It destroys existing event listeners
- ❌ Resets form inputs and dynamic states
- ❌ Hurts performance with large DOM updates
Luckily, there’s a better way: insertAdjacentHTML()
!
What’s Wrong with innerHTML +=
?
When you use innerHTML +=
, the browser:
- Serializes the entire container’s HTML into a string
- Appends your new HTML
- Re-parses everything and recreates the DOM
This means:
- All existing event listeners are wiped out
- JavaScript references to elements break
- Form inputs lose their values
Example of the Problem
// Add a button with a click listener
const container = document.getElementById("container");
container.innerHTML = `<button id="btn">Click Me</button>`;
document.getElementById("btn").addEventListener("click", () => alert("Clicked!"));
// Later, append more HTML (this BREAKS the listener!)
container.innerHTML += "<div>New content</div>";
// Now, the button’s click handler is GONE!
insertAdjacentHTML()
to the Rescue!
This method lets you insert HTML at specific positions without destroying the existing DOM.
Syntax
element.insertAdjacentHTML(position, htmlString);
Four Insertion Positions
Position | Description |
---|---|
'beforebegin' |
Before the element (as a sibling) |
'afterbegin' |
Inside the element, before its first child |
'beforeend' |
Inside the element, after its last child (most common) |
'afterend' |
After the element (as a sibling) |
Why It’s Better
✅ Preserves event listeners (no DOM rebuild)
✅ Faster performance (no full re-parse)
✅ More control over placement
Practical Example: Dynamic List Updates
❌ Bad Way (innerHTML +=
)
const list = document.getElementById("todo-list");
list.innerHTML += `<li>New Task</li>`; // Wipes existing listeners!
✅ Better Way (insertAdjacentHTML
)
const list = document.getElementById("todo-list");
list.insertAdjacentHTML("beforeend", `<li>New Task</li>`); // Safe!
✅ Best Way (With Event Delegation)
// Insert safely
list.insertAdjacentHTML("beforeend", `<li class="task">New Task</li>`);
// Handle clicks via delegation (works even for new items)
document.addEventListener("click", (e) => {
if (e.target.classList.contains("task")) {
e.target.classList.toggle("completed");
}
});
When Should You Use It?
✔ Appending dynamic content (chat messages, todos, etc.)
✔ Avoiding DOM reset side effects
✔ Optimizing frequent DOM updates
When Should You Avoid It?
✖ If you need to replace an entire element (use innerHTML
or replaceWith()
)
✖ If inserting unsanitized HTML (risk of XSS—always sanitize first!)
Performance Comparison
insertAdjacentHTML()
is much faster than innerHTML +=
because it doesn’t:
- Re-serialize the entire container
- Destroy and recreate DOM nodes
Benchmark Example
// Slow: innerHTML +=
for (let i = 0; i < 1000; i++) {
container.innerHTML += `<div>Item ${i}</div>`;
}
// Fast: insertAdjacentHTML
for (let i = 0; i < 1000; i++) {
container.insertAdjacentHTML("beforeend", `<div>Item ${i}</div>`);
}
Final Thoughts
-
Stop using
innerHTML +=
—it’s destructive and slow. -
Use
insertAdjacentHTML('beforeend', ...)
for safe, efficient DOM updates. - Combine with event delegation for bulletproof dynamic elements.
Try It Yourself!
Next time you dynamically add HTML, replace innerHTML +=
with insertAdjacentHTML()
and see the difference!
Top comments (0)