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.

explanation

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.