forEach

Efficiently renders dynamic lists with intelligent DOM diffing and key-based optimization.

API

function forEach<T>(
  each: T[] | Signal<T[]> | (() => T[]),
  use: (item: T, index: number) => VNodeValue
): (parent: HellaElement) => void
  • each: The data to iterate over. Can be a static array, a signal containing an array, or a function that returns an array.
  • use: A function that is called for each item in the array and should return a renderable VNode.
  • Returns: A function that HellaJS uses internally to append the list to a parent element.

TypeScript

The forEach function is generic. The type of item in the render function is inferred from the array type.

import { signal } from '@hellajs/core';
import { forEach, type VNodeValue } from '@hellajs/dom';

type User = { id: number; name: string };
const users = signal<User[]>([{ id: 1, name: 'Alice' }]);

// `item` is automatically typed as `User`
<ul>
  {forEach(users, (item) =>
    <li key={item.id}>{item.name}</li>
  )}
</ul>

Basic Usage

Provide forEach with an array and a render function. It’s designed to be used within JSX as a child element.

import { signal } from '@hellajs/core';
import { forEach } from '@hellajs/dom';

const items = signal(['Apple', 'Banana', 'Cherry']);

<ul>
  {forEach(items, (fruit, index) =>
    <li key={index}>{fruit}</li>
  )}
</ul>

Key Concepts

Key-Based Optimization

HellaJS automatically uses key or id properties from data items to efficiently track and update DOM nodes during list changes.

const items = signal([
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
]);

<ul>
  {forEach(items, (item) => 
    <li key={item.id}>{item.name}</li> // Uses item.id for tracking
  )}
</ul>

Reactive Arrays

When using signals, the entire array must be replaced (not mutated) to trigger reactivity and DOM updates.

const todos = signal([]);

// ✅ Correct: Create new array
todos([...todos(), newTodo]);

// ❌ Incorrect: Mutation doesn't trigger updates
todos().push(newTodo);

DOM Diffing

Intelligent diffing algorithm minimizes DOM operations by reusing existing elements where possible.

// When items change from [A, B, C] to [A, C, D].
// - A: reused (no DOM operation)
// - B: removed efficiently  
// - C: moved (minimal DOM operation)
// - D: added as new element

Important Considerations

Array Immutability

Always create new array instances for signal updates - mutations don’t trigger reactivity.

// ✅ Create new array
todos([...todos(), newItem]);
// ❌ Mutation doesn't trigger updates
todos().push(newItem);

Key Stability

Use stable, unique keys (like database IDs) rather than array indices for optimal performance.

// ✅ Stable keys
{forEach(items, (item) => <li key={item.id}>{item.name}</li>)}
// ❌ Index keys cause re-renders
{forEach(items, (item, index) => <li key={index}>{item.name}</li>)}

Performance

Large lists may benefit from virtualization - forEach renders all items in the DOM.

// ✅ Consider virtualization for lists > 1000 items
const visibleItems = computed(() => items().slice(start(), end()));