How to Filter Array Data and Render Dynamic Content in the DOM: A Complete Guide

Use Array.prototype.filter() to create subsets of your data array, then iterate over the filtered results with forEach() to generate DOM nodes using HTML templates or createElement().

The Advanced To-Do project in the jisan-mia/dom-projects repository demonstrates a production-ready pattern for filtering array data and rendering dynamic content in the DOM using vanilla JavaScript. This approach separates data management from presentation logic, creating maintainable code that handles complex UI state without external libraries.

Setting Up the Data Source and Persistence

Initializing the Array from localStorage

All to-do items are stored in a central todoList array. The application initializes this array from localStorage on page load to maintain state across sessions.

In projects/advanced-todo/script.js at line 10, the data layer is established:

let todoList = JSON.parse(localStorage.getItem("todoList")) || [];

The helper functions saveTodo() and getTodo() handle persistence. When the array mutates, saveTodo() serializes the data to localStorage, ensuring the UI always reflects the last saved state.

Filtering Array Data Based on User Selection

Implementing the Filter Logic with Array.prototype.filter()

When users click filter buttons ("All", "Active", "Completed"), the application stores the chosen role in activeFilterRole and invokes showFilteredTodos(). This function uses Array.prototype.filter() to create subsets based on the isCompleted property.

From projects/advanced-todo/script.js at lines 113 and 120:

// Filter for active (not completed) todos
if (activeFilterRole === 'active') {
  const activeTodos = todoList.filter(todo => !todo.isCompleted);
  activeTodos.forEach(renderTodo);
}

// Filter for completed todos
if (activeFilterRole === 'completed') {
  const completedTodos = todoList.filter(todo => todo.isCompleted);
  completedTodos.forEach(renderTodo);
}

This pattern creates a new array containing only items matching the criteria, leaving the original todoList unchanged for other operations.

Handling Item Deletion with Filter

The same filtering technique removes items from the dataset. When a user deletes a to-do, the application filters out the item with the matching id and reassigns the result to todoList.

From projects/advanced-todo/script.js at line 37:

function deleteTodo(todoId) {
  todoList = todoList.filter(t => t.id != todoId);
  saveTodo();
  showFilteredTodos();
}

After filtering, saveTodo() persists the updated array, and showFilteredTodos() triggers a re-render to reflect the deletion in the DOM.

Rendering Dynamic Content in the DOM

Using HTML Templates for Efficient DOM Updates

The renderTodo() function generates DOM elements using the HTML <template> element, which provides a performant way to create repetitive structures without parsing HTML strings repeatedly.

From projects/advanced-todo/script.js at line 76 and the template usage around line 160:

function renderTodo(todo) {
  const template = document.querySelector('[data-template-todo-item]');
  const templateClone = template.content.cloneNode(true);
  
  const listItem = templateClone.querySelector('.list-item');
  listItem.dataset.todoId = todo.id;
  
  templateClone.querySelector('[data-list-item-text]').innerText = todo.name;
  templateClone.querySelector('[data-list-item-checkbox]').checked = todo.isCompleted;
  
  list.appendChild(templateClone);
}

This approach clones the template content, populates it with data from the filtered array, and appends it to the DOM container. Using templates ensures event listeners and structure remain consistent across all rendered items.

Connecting Filtered Data to the Render Pipeline

The showFilteredTodos() function serves as the bridge between data filtering and DOM rendering. It clears the current list, determines which filter is active, applies the appropriate filter() operation, and iterates over results with forEach():

function showFilteredTodos() {
  list.innerHTML = '';
  
  if (activeFilterRole === 'all') {
    todoList.forEach(renderTodo);
  } else if (activeFilterRole === 'active') {
    const activeTodos = todoList.filter(todo => !todo.isCompleted);
    activeTodos.forEach(renderTodo);
  } else if (activeFilterRole === 'completed') {
    const completedTodos = todoList.filter(todo => todo.isCompleted);
    completedTodos.forEach(renderTodo);
  }
}

This pattern ensures the DOM always reflects the current filter state while maintaining a single source of truth in the todoList array.

Summary

  • Store data in a central array (todoList) and persist it with localStorage via saveTodo() and getTodo() in projects/advanced-todo/script.js.
  • Use Array.prototype.filter() to create subsets of data based on user selection (active, completed) without mutating the original array.
  • Handle deletions by reassigning the array to a filtered version that excludes the removed item: todoList = todoList.filter(t => t.id != todoId).
  • Render efficiently with HTML templates by cloning template.content, populating data attributes, and appending to the DOM container.
  • Separate concerns by keeping filtering logic in showFilteredTodos() and rendering logic in renderTodo() for maintainable code architecture.

Frequently Asked Questions

Why use Array.prototype.filter() instead of for loops?

Array.prototype.filter() creates a new array containing only matching elements without mutating the original data source. This functional approach produces cleaner, more readable code than manual for loops with conditional pushing. In the Advanced To-Do project, this immutability ensures that todoList remains intact while generating temporary subsets for "Active" or "Completed" views.

How does the HTML template element improve performance?

The <template> element stores HTML fragments in memory without rendering them immediately. When renderTodo() clones template.content.cloneNode(true), it creates a deep copy of the structure without parsing HTML strings or triggering reflows repeatedly. This approach is significantly faster than innerHTML concatenation in loops and ensures consistent event listener binding across all dynamically generated list items.

Can this pattern handle large datasets efficiently?

For datasets exceeding several hundred items, the current implementation in projects/advanced-todo/script.js may experience performance degradation during full re-renders because showFilteredTodos() clears innerHTML and rebuilds the entire list. To optimize for large datasets, implement virtual scrolling or document fragment batching, and consider updating only changed DOM nodes rather than full list regeneration when filtering.

How do I persist filtered state across page reloads?

The current implementation persists only the todoList array and restores the default "All" filter on reload. To maintain the active filter state, store activeFilterRole in localStorage alongside the todos. On initialization, retrieve the saved filter value and call showFilteredTodos() with the restored state instead of defaulting to "All".

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →