How Updates Work
What happens between changing a property and seeing the DOM update, in four steps.
The Update Pipeline
Every DOM update in WildflowerJS follows the same four-step pipeline:
This pipeline runs automatically. You never interact with it directly. Just change state and the framework handles the rest.
Step by Step
1. State Change: Proxy Interception
WildflowerJS wraps component state in a JavaScript Proxy. When you assign a property, the Proxy's set trap fires:
// You write:
this.count++
// The Proxy intercepts this as:
// set(target, 'count', 1)
// → records that 'count' changed
// → schedules an update
This works for any mutation: direct assignment, nested property changes, array methods:
this.count++ // direct assignment
this.user.name = "Jane" // nested property
this.items.push({ text: "New" }) // array mutation
this.items.splice(2, 1) // array splice
All of these are intercepted automatically. There is no special syntax, wrapper function, or immutable update pattern to follow.
2. Dependency Lookup: Path-Level Tracking
When the framework processes a binding like data-bind="count", it records that this specific DOM node depends on the count property. This creates a mapping:
Property Path → DOM Nodes
───────────── ─────────
count → span#counter-display
user.name → h1#greeting, span#profile-name
items → ul#todo-list (list binding)
items[].text → each li text node
When count changes, the framework looks up exactly which nodes depend on it. Nothing walks the tree, re-renders a component, or diffs. Just a direct lookup.
Computed properties live in the same graph. State, computeds, effects, and bindings are all nodes in one dependency graph, so a binding that reads a computed depends, through it, on the state the computed reads; a change wakes the chain and nothing else.
3. Effects Scheduled: Automatic Batching
Changes made in the same synchronous block coalesce. Some updates apply at the moment of the write and others are queued and flushed together on a microtask; which is which is an internal optimization detail that may change between releases. The guarantee to build on is simple: by the time your await, timeout, or next event handler runs, the DOM reflects your state, and within any frame the user sees a consistent picture.
// All three changes happen synchronously
this.firstName = "Jane"
this.lastName = "Smith"
this.email = "jane@example.com"
// The DOM settles with the final values.
// No half-applied state ever reaches the screen.
This is automatic. You never need to wrap changes in a batch function or call a flush method, and you should not build on intra-tick DOM timing; coalescing happens on its own.
4. Targeted DOM Updates: Surgical Precision
When the microtask fires, only the specific DOM nodes that depend on changed properties are updated:
<div data-component="profile">
<h1 data-bind="name"></h1> <!-- Updated ✓ -->
<p data-bind="bio"></p> <!-- NOT touched -->
<span data-bind="email"></span> <!-- NOT touched -->
</div>
If only name changed, only the <h1> is touched. The <p> and <span> are untouched. The framework doesn't even look at them.
How This Differs from Virtual DOM
Virtual DOM (React, Vue)
- State changes
- Re-render entire component to virtual tree
- Diff old virtual tree vs new virtual tree
- Compute minimal set of DOM patches
- Apply patches to real DOM
Work is proportional to component size
WildflowerJS
- State changes
- Look up which bindings depend on changed path
- Update those DOM nodes directly
Work is proportional to number of changes
Virtual DOM frameworks do work proportional to component size (they must re-render and diff the entire component). WildflowerJS does work proportional to what actually changed: if one property changed and two DOM nodes depend on it, only those two nodes are touched regardless of how large the component is.
List Updates: Operation Detection
Lists get special treatment. Instead of re-rendering the entire list when an array changes, WildflowerJS detects what kind of operation occurred and applies the minimal DOM change:
| Array Operation | DOM Response |
|---|---|
items.push(item) |
Append one new node |
items.pop() |
Remove last node |
items.splice(2, 1) |
Remove node at index 2 |
items.unshift(item) |
Prepend one new node |
items[i] = newItem |
Update node at index i in place |
items = [...items].sort(fn) |
Reconcile with minimal moves |
Nested Reactivity
The Proxy wrapping is deep: it covers nested objects and arrays automatically:
// All of these trigger updates automatically:
this.user.address.city = "Portland" // deep nested property
this.columns[2].cards.push(newCard) // nested array mutation
this.settings = { ...this.settings, theme: "dark" } // object replacement
There is no depth limit. The framework tracks changes at whatever level they occur and updates only the bindings that depend on the changed path.