Why Pools? LITE+

WildflowerJS gives you two reactivity modes for collections. data-list is push reactivity: mutate state, the DOM updates automatically. data-pool is pull reactivity: mutate plain objects, tell the pool what changed, it updates on the next frame. Both are reactive. Same template syntax. Different tradeoff.

The Insight

Push reactivity is convenient: mutate a property, the DOM updates. But that convenience has a cost: every item gets wrapped in a reactive proxy, every property write goes through a trap, and every change triggers dependency tracking.

For a 20-item settings panel, that cost is invisible. For a 10,000-row table or a real-time dashboard, it adds up.

WildflowerJS lets you choose. data-pool is still reactive (state changes still reach the DOM) but through a pull model instead of push. You trade automatic change detection for explicit markDirty() calls, and get plain-object mutation speed in return.

Two Reactivity Modes

data-list (Push) data-pool (Pull)
Item type Reactive proxy objects Plain JS objects
Update trigger Property change via Proxy requestAnimationFrame batch
Update cost Per-property (precise, zero idle cost) Per-frame (batched, throughput-optimized)
Template scope Full: item properties, parent state, stores, computed Item properties + shared props (via pool.props)
Best for Interactive items: forms, inline editing, two-way binding Large datasets, real-time data, animation, performance-sensitive CRUD
Template syntax Identical: data-bind, data-bind-style, data-bind-class, data-bind-attr, data-show

You pick the mode that fits the job, with no escape hatches or external libraries involved. Swap data-list for data-pool and you switch from push to pull. The template stays the same.

Use Cases

Large Collections

Log tables, audit trails, activity feeds, search results, product catalogs, leaderboards: any collection where explicit update control beats automatic change detection. Pool renders with zero proxy overhead, handles full CRUD via its API, and offers optional entity recycling for pagination.

Real-Time Data

Stock tickers, monitoring dashboards, IoT sensor readings, WebSocket feeds: data that arrives pre-formed and just needs to be displayed. Mutate the plain object, the pool flushes it to DOM on the next frame. There are no reactive notifications and no microtask scheduling. With data-pool-fps throttling, each pool can update at its own frequency.

Per-Frame Animation

Particle systems, game entities, physics simulations, data visualizations: hundreds of DOM elements updating every frame at native refresh rate. This is where pools originally started, and where no other framework can follow. Every DOM element is a potential animation target. No canvas. No WebGL. No escape hatches.

Lite Lorenz Attractor

900 DOM particles tracing strange attractors in 3D. Radial glow, depth-mapped color, perspective projection. Pure math, pure DOM, 60fps.

Full Mission Control Dashboard

Pool-rendered bar chart, latency scatter plot, and traffic heatmap alongside data-list feeds and SVG gauges. Zero chart libraries.

Lite Analytics Dashboard

Pool-rendered user tables and activity feeds alongside Chart.js integration. Demonstrates pools as an explicit-control alternative to data-list for collection rendering.

Lite Boids Flocking

800 autonomous agents with spatial grid neighbor search, multi-flock coloring, predator/prey dynamics. CSS triangles as DOM elements.

Performance-Sensitive Lists

Pools aren't just for display-only data. They handle full CRUD operations (create, select, update, remove, swap) with the same template syntax as data-list. The tradeoff: you call markDirty() explicitly instead of relying on automatic change detection. In return, every mutation skips the proxy trap pipeline entirely.

Here's the same interactive list implemented both ways. The template is nearly identical: two attribute changes and a props. prefix:

data-list
<tbody data-list="rows" data-key="id">
  <template>
    <tr data-bind-class="id === selectedId ? 'danger' : ''">
      <td data-bind="label"></td>
      <td><a data-action="select">Select</a></td>
      <td><a data-action="remove">Remove</a></td>
    </tr>
  </template>
</tbody>
data-pool
<tbody data-pool="rows" data-key="id" data-pool-static>
  <template>
    <tr data-bind-class="id === props.selectedId ? 'danger' : ''">
      <td data-bind="label"></td>
      <td><a data-action="select">Select</a></td>
      <td><a data-action="remove">Remove</a></td>
    </tr>
  </template>
</tbody>

The JavaScript differs in how you express mutations:

data-list: automatic
// Selection: set state, framework handles it
this.selectedId = item.id;

// Update: mutate through proxy
this.rows[i].label += ' !!!';

// Remove: splice the array
this.rows.splice(index, 1);
data-pool: explicit
// Selection: set prop, mark only 2 rows dirty
pool.props.selectedId = item.id;
pool.markDirty(prevId);
pool.markDirty(item.id);

// Update: mutate plain object, mark dirty
item.label += ' !!!';
pool.markDirty(item.id);

// Remove: one call
pool.remove(item.id);

Where this matters most:

  • Selection at scale: In a pool, you mark exactly 2 rows dirty: the old and new selection. Lists also achieve O(2) selection through their update routing, but pools reach it with zero framework machinery.
  • Mutation overhead: A list property write goes through a reactive proxy trap; for a plainly bound field the framework turns that into a single targeted DOM write, which is why lists now hold their own at benchmark scale. Pool items are plain objects with no proxy at all: mutate directly, markDirty() once, and the pool flushes every dirty row in one pass per frame. At thousands of writes per frame, that difference is the frame budget.
  • Bulk operations: Pool clear() and add() skip even the per-row binding registration that lists maintain.

Pool vs List: Benchmark Comparison

Measured July 2026 (v1.2 line) using the krausest js-framework-benchmark harness with identical templates. Negative = pool faster:

Operation Pool vs List Notes
Create 1,000-4%Both use the bulk creation path
Update every 10th+1%Equivalent; the list's targeted writers close the old gap
Select row-6%Both O(2); pool with zero machinery
Swap rows-1%Equivalent
Remove row-4%
Create 10,000-5%
Append 1,000-9%
Clear all-10%No per-row registration teardown

At list-shaped workloads (discrete user actions on keyed rows) the two renderers are now close: pools hold a modest edge on most operations, and data-list's targeted update routing has closed the gaps that used to define this table. That narrows what pools are for:per-frame work. When thousands of entities change every animation frame, the pool's plain objects and single flush pass deliver roughly 20% higher sustained frame rates than data-list in our Lorenz particle benchmark (4,000 to 12,000 animated elements), and that gap widens with entity count. Use data-list for lists; reach for data-pool when your list becomes a simulation.

The Architecture

Pools bypass the Proxy-based reactivity system entirely. Entity objects are plain JavaScript, with no getters, setters, or observable wrappers. Your code mutates properties directly, and the pool renderer reads those values and writes them to the DOM in a single batch.

// Display table: push plain objects, pool stamps them into DOM
data.forEach(row => this.pools.rows.push(row));

// Real-time update: mutate the object, pool picks it up next frame
this.pools.rows.update('row-42', { status: 'active' });

// Animation: mutate in a tick loop, pool flushes every frame
tick(dt) {
    var t = dt / 16.67;
    for (const p of this.pools.particles) {
        p.x += p.vx * t;
        p.y += p.vy * t;
    }
}

This is why it's fast. There is no notification system, dependency tracking, or change detection between your code and the DOM. The pool reads, diffs the previous values, and writes only what changed. The cost is proportional to the number of changed properties, not the number of entities.

Pools aren't a separate concept from the rest of the framework. They're entities, in exactly the sense that components, stores, and plugins are entities. The per-item declaration inside a pool's entity: block uses the same state, computed, and methods shape that components, stores, and plugins use at the top level. What makes pools different is multiplicity (one entity block describes many items, keyed by id), reactivity mode (items are plain JavaScript objects, pulled on flush, rather than Proxy-tracked state pushed on mutation), and timing (batched per-frame DOM writes rather than synchronous ones). See Defining an Entity for the full surface.

How Other Frameworks Handle This

They don't offer a choice. Every list item gets the full reactive treatment, whether it needs it or not.

Framework Large Collections 60fps Animation
React Full component per item, React.memo to reduce re-renders "Don't use setState in useFrame." Use refs + requestAnimationFrame to bypass React entirely.
Vue Full reactive proxy per item via v-for toRaw() and shallowRef() as "escape hatches." External animation libraries.
Solid Fine-grained signals per item via <For> Community recommends canvas-based rendering (tsParticles), not DOM.
Svelte Full reactive binding per item via {#each} CSS animations recommended. No per-frame DOM rendering primitive.
WildflowerJS data-pool: zero proxy overhead, plain objects data-pool + tick(): same primitive, native frame rate

No other framework offers two rendering modes sharing the same template syntax. You don't escape the framework; you tell it how much reactivity you need.

When to Use Each

Use data-pool when:

  • You have large collections (100+ items) where proxy overhead matters
  • You need high-frequency updates (real-time data, animation, live feeds)
  • You want explicit control over what gets re-rendered (markDirty())
  • You're paginating through data (entity recycling reuses DOM nodes)
  • Items need shared state accessible via pool.props (selection, filters, mode flags)

Use data-list when:

  • Items need two-way binding (data-model)
  • Items need to reference parent computed properties or store data in expressions
  • Items need conditional rendering (data-render) or nested lists
  • You want zero-ceremony reactivity: mutate state, DOM updates automatically
  • The collection is small and interactive (forms, settings panels, editable tables)
Start with data-list; it's the right default. Use data-pool when you need more performance or explicit control over what updates.
Ready to build? See Entity Pools for the full guide, Pool API for the complete reference, or Pool Performance for optimization patterns.