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

> Master filtering array data and rendering dynamic DOM content. Learn to use Array.prototype.filter() and forEach() to build interactive web applications effectively.

- Repository: [Jisan Mia/dom-projects](https://github.com/jisan-mia/dom-projects)
- Tags: tutorial
- Published: 2026-03-04

---

**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`](https://github.com/jisan-mia/dom-projects/blob/main/projects/advanced-todo/script.js) at line 10, the data layer is established:

```javascript
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`](https://github.com/jisan-mia/dom-projects/blob/main/projects/advanced-todo/script.js) at lines 113 and 120:

```javascript
// 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`](https://github.com/jisan-mia/dom-projects/blob/main/projects/advanced-todo/script.js) at line 37:

```javascript
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`](https://github.com/jisan-mia/dom-projects/blob/main/projects/advanced-todo/script.js) at line 76 and the template usage around line 160:

```javascript
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()`:

```javascript
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`](https://github.com/jisan-mia/dom-projects/blob/main/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`](https://github.com/jisan-mia/dom-projects/blob/main/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".