Event Bubbling

Event bubbling allows child components to send notifications up through the component hierarchy, reaching ancestors multiple levels above automatically.

The Basics

By default, notifyParent() only sends events to the immediate parent. Enable bubbling with the third parameter to propagate events up the entire hierarchy:

// Notify immediate parent only (default)
this.notifyParent('eventType', data);

// Bubble through all ancestors
this.notifyParent('eventType', data, true);

Bubbling in Action

When bubbling is enabled, events travel up through each ancestor's receiveFromChild() method until stopped or the root is reached:

<div id="root-app">
    <h2>Root - Errors: {{errorCount}}</h2>

    <div id="parent-app">
        <h3>Parent - Status: {{status}}</h3>

        <div id="child-app">
            <h4>Child</h4>
            <button data-pac-bind="click: triggerError">Trigger Error</button>
        </div>
    </div>
</div>

<script>
    const child = wakaPAC('#child-app', {
        triggerError() {
            // Event bubbles to parent, then grandparent
            this.notifyParent('error', {
                message: 'Validation failed',
                field: 'email'
            }, true);
        }
    });

    const parent = wakaPAC('#parent-app', {
        status: 'OK',

        receiveFromChild(eventType, data, childComponent) {
            if (eventType === 'error') {
                this.status = 'Child error: ' + data.field;
                console.log('Parent received error');
                // Event continues to grandparent automatically
            }
        }
    });

    const root = wakaPAC('#root-app', {
        errorCount: 0,

        receiveFromChild(eventType, data, childComponent) {
            if (eventType === 'error') {
                this.errorCount++;
                console.log('Root caught error:', data.message);
            }
        }
    });
</script>

Controlling Propagation

Any ancestor can stop the event from bubbling further by returning false from receiveFromChild():

<div id="root-app">
    <h2>Root</h2>
    <p>{{rootMessage}}</p>

    <div id="middle-app">
        <h3>Middle</h3>
        <p>{{middleMessage}}</p>

        <div id="child-app">
            <h4>Child</h4>
            <button data-pac-bind="click: sendEvent">Send Event</button>
        </div>
    </div>
</div>

<script>
    const child = wakaPAC('#child-app', {
        sendEvent() {
            this.notifyParent('notification', {
                text: 'Hello from child'
            }, true);
        }
    });

    const middle = wakaPAC('#middle-app', {
        middleMessage: '',

        receiveFromChild(eventType, data, childComponent) {
            if (eventType === 'notification') {
                this.middleMessage = 'Received: ' + data.text;

                // Stop propagation - root never receives this event
                return false;
            }
        }
    });

    const root = wakaPAC('#root-app', {
        rootMessage: 'Waiting...',

        receiveFromChild(eventType, data, childComponent) {
            // Never called because middle stopped propagation
            this.rootMessage = 'Should not see this';
        }
    });
</script>

Propagation Control

Return Value Behavior
false Stop bubbling immediately - event is handled
true Continue bubbling to next ancestor
undefined (no return) Continue bubbling (default behavior)

When to Use Bubbling

Scenario Recommendation
Direct parent-child communication No - Default behavior is simpler
Global error handling at root Yes - Centralized error logging/handling
Authentication/logout events Yes - App-wide state changes
Form field validation No - Parent form handles directly
Theme/language changes Yes - Root manages global settings
Component-specific actions No - Keep communication local

Best Practices

  • Default to non-bubbling: Only enable bubbling for events that genuinely need to reach distant ancestors
  • Stop when handled: Return false once the event is fully processed to prevent unnecessary propagation
  • Use consistent event names: Establish naming conventions for bubble events (e.g., "error", "auth:logout", "theme:change")
  • Document bubble behavior: Clearly indicate which events bubble and why in your component documentation
  • Consider alternatives: For truly global state, a dedicated state manager or direct root access might be clearer than relying on bubbling
  • Avoid overuse: Too many bubbling events make the event flow hard to trace and debug

Common Patterns

Centralized Error Handler

// Root component catches all errors
const root = wakaPAC('#app', {
    errors: [],

    receiveFromChild(eventType, data, childComponent) {
        if (eventType === 'error') {
            this.errors.push({
                timestamp: new Date(),
                source: childComponent.container.id,
                ...data
            });
            // Stop bubbling - errors handled at root
            return false;
        }
    }
});

// Any descendant can report errors
this.notifyParent('error', { message: 'Failed to save' }, true);

Authentication State

// Deep nested component triggers logout
this.notifyParent('auth:logout', { reason: 'session_expired' }, true);

// Root clears user state
receiveFromChild(eventType, data) {
    if (eventType === 'auth:logout') {
        this.currentUser = null;
        this.redirectToLogin();
        return false; // Handled at root
    }
}
Performance Note: Bubbling adds minimal overhead - each ancestor's receiveFromChild() is called synchronously in sequence. Stopping propagation early with return false prevents unnecessary ancestor processing.