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()));