How Reactivity Works
WakaPAC's reactivity system automatically synchronizes your data with the DOM. Change a property, and the UI updates - no manual DOM manipulation required.
The Basics
When you create a WakaPAC component, all properties in your abstraction object become reactive:
wakaPAC('#app', {
count: 0,
increment() {
this.count++; // Change is detected, DOM updates scheduled
}
});
WakaPAC's reactivity engine:
- Creates proxies for all properties in your abstraction object
- Tracks dependencies - which DOM elements reference which properties
- Queues updates when properties change, avoiding redundant operations
- Batches DOM changes in microtasks for optimal performance
- Updates selectively - only affected DOM elements are modified
The Reactivity Pipeline
Deep Reactivity
Nested Objects
WakaPAC recursively wraps nested objects in proxies, making changes at any depth reactive:
<div id="app">
<p>{{user.name}}</p>
<p>Theme: {{user.preferences.theme}}</p>
<button data-pac-bind="click: changeTheme">Toggle Theme</button>
</div>
<script>
wakaPAC('#app', {
user: {
name: 'John',
preferences: {
theme: 'dark'
}
},
changeTheme() {
// Deep property changes trigger reactivity
this.user.preferences.theme =
this.user.preferences.theme === 'dark' ? 'light' : 'dark';
}
});
</script>
Array Mutations
WakaPAC intercepts array mutator methods (push, pop, splice, shift, unshift, sort, reverse) to trigger updates automatically:
<div id="app">
<ul data-pac-bind="foreach: todos" data-pac-item="todo">
<li>{{todo.text}} (Item {{$index}})</li>
</ul>
<button data-pac-bind="click: addTodo">Add Todo</button>
</div>
<script>
wakaPAC('#app', {
todos: [
{ text: 'Learn WakaPAC', completed: false },
{ text: 'Build an app', completed: false }
],
addTodo() {
// Array mutations automatically trigger re-render of the list
this.todos.push({
text: 'New todo',
completed: false
});
}
});
</script>
Objects Within Arrays
Objects stored in arrays are also wrapped in proxies, making their property changes reactive:
<div id="app">
<div data-pac-bind="foreach: todos" data-pac-item="todo" >
<input type="checkbox" data-pac-bind="checked: todo.completed">
<span>{{todo.text}} (Item {{$index}})</span>
<button data-pac-bind="click: toggleTodo">Toggle</button>
</div>
</div>
<script>
wakaPAC('#app', {
todos: [
{ text: 'Task 1', completed: false },
{ text: 'Task 2', completed: false }
],
toggleTodo(todo, index) {
// Modifying object properties inside arrays triggers reactivity
todo.completed = !todo.completed;
}
});
</script>
Foreach Index Variable
Within a foreach loop, WakaPAC provides the special variable $index containing the current iteration index (0-based). The $ prefix distinguishes it from user-defined properties, preventing naming conflicts.
You can customize the index variable name using the data-pac-index attribute:
<div data-pac-bind="foreach: items" data-pac-index="i">
<span>Item {{i}}: {{item}}</span>
</div>
Reactivity Caveats
Array Index Assignment
Direct array index assignment is reactive, but sparse array assignments may not behave as expected:
// ✅ Reactive
this.todos[0] = newTodo;
// ⚠️ Creates sparse array, may not update correctly
this.todos[100] = newTodo;
Use push(), splice(), or reassign the array for complex operations.
Primitive Value Reassignment
When passing primitive values to child components or functions, changes to the original won't affect the copy:
let count = this.count; // count is now a copy
count++; // this.count is unchanged
Always modify properties directly on this to maintain reactivity.
Escaping the Proxy
Every reactive value in the abstraction exposes a unwrap() method that returns the underlying raw object. This is useful when you need to compare or pass the original object reference to code that should not interact with the proxy — for example, a plugin performing identity comparisons.
wakaPAC('#app', {
item: { id: 1, name: 'Example' },
check() {
// this.item is a reactive proxy
// this.item.unwrap() is the original plain object
const raw = this.item.unwrap();
console.log(raw === this.item); // false — proxy !== raw object
}
});
Calling unwrap() does not remove reactivity from the property — the abstraction still tracks the value normally. It simply gives you a reference to the underlying object for the duration of that call. Mutations made to the raw object directly will not trigger DOM updates.
Don't mutate the raw object
Changes made directly to a raw object bypass the reactivity system and will not update the DOM. Only use unwrap() for reading or identity comparisons, not for writing.