Coding, Insights, and Digital Discoveries 👩🏻‍💻

From Stay-at-Home Mom to a Developer After 50

Published on

Better Code for Dynamically Creating Elements in Vanilla JavaScript

create dynamic elements

I coded a To-Do-List app using vanilla javascript. My goal for this project is to refresh my knowledge of vanilla JavaScript, as frequent use of React.js and Next.js can make clean JavaScript coding habits feel unfamiliar.

During the development process, I needed to dynamically generate elements when adding new tasks. This made me realize that even basic DOM knowledge is worth revisiting.

Let's compare two functions that dynamically create an <li> element for listing new tasks in the UI.

Using innerHTML

The first function is as follows:

function createTaskElement(task) {
    const li = document.createElement("li");
    li.innerHTML = `
        <input type="checkbox" class="task-checkbox" ${task.completed ? "checked" : ""}>
        <span class="task-text">${task.text}</span>
    `;
    
    if (task.completed) {
        completedTaskList.appendChild(li);
    } else {
        taskList.appendChild(li);
    }
}

This version uses innerHTML to add he task text. However, this approach has several drawbacks:

  • Security risk : If task.text contains malicious code, it can introduce an XSS (Cross-Site Scripting) vulnerability. For security reasons, this method is not recommended in production.
  • Performance Concern : innerHTML parses HTML strings, which is generally slower than direct DOM manipulation.
  • Limited reusability and extensibility : This function is tightly coupled to the HTML structure, making it harder to modify or add event handling later. Although it is concise and readable for simple cases, it becomes difficult to debug and maintain as the code grows.

Using a Modular Approach

Now, let's check out an improved version:

function createTaskElement(task) {
    // Create <li> element
    const li = document.createElement("li");

    // Create checkbox
    const checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.className = "task-checkbox";
    checkbox.checked = task.completed;
    checkbox.addEventListener("change", (e) => {
        animateTaskCompletion(li, e.target.checked);
        updateTaskStatus(task.text, e.target.checked);
    });

    // Create task text span
    const taskText = document.createElement("span");
    taskText.className = "task-text";
    taskText.textContent = task.text;

    // Append elements to <li>    
    li.append(checkbox, taskText)

    // Append <li> to the correct list
    (task.completed ? completedTaskList : taskList).appendChild(li);

    return li;
}

Why This Version Is Better

  1. Improved Security : Instead of using innerHTML, this approach uses textContent, which automatically escapes special characters, making it safer against XSS attacks.
  2. Better Performance : createElement() directly manipulates the DOM, which is more efficient than using innerHTML to parse and insert HTML strings.
  3. Easier Event Handling : This approach allows event listeners to be added directly to elements, making it more scalable for adding rich features.
  4. Modular and Maintainable : The function follows the principle of separation of concerns—DOM creation, event handling, and animations are well-structured.
  5. More Readable and Debuggable : While this version is more verbose, its clear structure makes it easier to understand, debug, and modify.
  6. Supports Reusability : The function includes a return li statement, making it more flexible. Returning the element allows it to be referenced later, like in the following example:
const newTaskElement = createTaskElement(task);
// Want to do something with the element later
newTaskElement.scrollIntoView();

By returning the newly created element, this function adheres to the principle that if a function creates something, it should return it. This improves testability and allows for method chaining if needed.


This modular approach provides better security, performance, maintainability, and flexibility—making it the preferred choice for this vanilla JavaScript project.

← See All Posts