# LiveComponents Events Reference Complete reference for all client-side events and lifecycle hooks with examples and best practices. ## Table of Contents - [Lifecycle Events](#lifecycle-events) - [livecomponent:init](#livecomponentinit) - [livecomponent:mounted](#livecomponentmounted) - [livecomponent:updated](#livecomponentupdated) - [livecomponent:unmounted](#livecomponentunmounted) - [Action Events](#action-events) - [livecomponent:action-start](#livecomponentaction-start) - [livecomponent:action-executed](#livecomponentaction-executed) - [livecomponent:action-failed](#livecomponentaction-failed) - [State Events](#state-events) - [livecomponent:state-changed](#livecomponentstate-changed) - [livecomponent:prop-updated](#livecomponentprop-updated) - [Network Events](#network-events) - [livecomponent:request-start](#livecomponentrequest-start) - [livecomponent:request-complete](#livecomponentrequest-complete) - [livecomponent:request-failed](#livecomponentrequest-failed) - [livecomponent:batch-dispatched](#livecomponentbatch-dispatched) - [Rendering Events](#rendering-events) - [livecomponent:render-start](#livecomponentrender-start) - [livecomponent:render-complete](#livecomponentrender-complete) - [livecomponent:fragment-updated](#livecomponentfragment-updated) - [SSE Events](#sse-events) - [livecomponent:sse-connected](#livecomponentsse-connected) - [livecomponent:sse-disconnected](#livecomponentsse-disconnected) - [livecomponent:sse-message](#livecomponentsse-message) - [livecomponent:sse-error](#livecomponentsse-error) - [Upload Events](#upload-events) - [livecomponent:upload-start](#livecomponentupload-start) - [livecomponent:upload-progress](#livecomponentupload-progress) - [livecomponent:upload-complete](#livecomponentupload-complete) - [livecomponent:upload-failed](#livecomponentupload-failed) - [Error Events](#error-events) - [livecomponent:error](#livecomponenterror) - [livecomponent:validation-error](#livecomponentvalidation-error) - [livecomponent:rate-limit-exceeded](#livecomponentrate-limit-exceeded) --- ## Lifecycle Events ### livecomponent:init **Fired**: When a LiveComponent is discovered in the DOM but before initialization. **Use Case**: Prepare component before mounting, modify initial state. **Event Detail**: ```typescript { componentId: string; // Unique component ID element: HTMLElement; // Component root element props: object; // Initial component props } ``` **Example**: ```javascript window.addEventListener('livecomponent:init', (event) => { const { componentId, element, props } = event.detail; console.log('Initializing component:', componentId); // Modify initial props if (props.theme === undefined) { props.theme = localStorage.getItem('theme') || 'light'; } // Add custom data attributes element.dataset.initialized = Date.now(); }); ``` **Common Use Cases**: - Restore state from localStorage - Set default props from user preferences - Initialize third-party libraries - Track component initialization analytics --- ### livecomponent:mounted **Fired**: After a LiveComponent is fully mounted and ready for interaction. **Use Case**: Initialize JavaScript behaviors, start timers, load external data. **Event Detail**: ```typescript { componentId: string; // Unique component ID element: HTMLElement; // Component root element component: Component; // Component instance props: object; // Current component props } ``` **Example**: ```javascript window.addEventListener('livecomponent:mounted', (event) => { const { componentId, element, component } = event.detail; // Initialize tooltips const tooltips = element.querySelectorAll('[data-tooltip]'); tooltips.forEach(el => new Tooltip(el)); // Start auto-refresh timer if (component.config.autoRefresh) { setInterval(() => { component.executeAction('refresh'); }, component.config.refreshInterval || 30000); } // Track mount analytics analytics.track('component_mounted', { component_type: componentId, mount_time: Date.now() }); }); ``` **Common Use Cases**: - Initialize third-party plugins (charts, maps, editors) - Start polling/auto-refresh timers - Bind custom event listeners - Load deferred content - Track analytics --- ### livecomponent:updated **Fired**: After a LiveComponent has been updated (re-rendered). **Use Case**: React to state changes, update third-party integrations, track updates. **Event Detail**: ```typescript { componentId: string; // Unique component ID element: HTMLElement; // Component root element component: Component; // Component instance previousProps: object; // Props before update currentProps: object; // Props after update changedProps: string[]; // Names of changed props fragments: string[]; // Updated fragments (if partial render) } ``` **Example**: ```javascript window.addEventListener('livecomponent:updated', (event) => { const { componentId, previousProps, currentProps, changedProps, fragments } = event.detail; // Re-initialize elements in updated fragments if (fragments.includes('chart-container')) { const chartEl = document.querySelector('[data-lc-fragment="chart-container"]'); initializeChart(chartEl, currentProps.chartData); } // Track specific prop changes if (changedProps.includes('status')) { console.log('Status changed:', previousProps.status, '→', currentProps.status); if (currentProps.status === 'completed') { showSuccessNotification(); } } // Update document title if (changedProps.includes('unreadCount')) { document.title = currentProps.unreadCount > 0 ? `(${currentProps.unreadCount}) Messages` : 'Messages'; } }); ``` **Common Use Cases**: - Re-initialize third-party components in updated fragments - Trigger animations on state changes - Update browser title/favicon based on state - Track state change analytics - Sync with external systems --- ### livecomponent:unmounted **Fired**: Before a LiveComponent is removed from the DOM. **Use Case**: Cleanup timers, remove event listeners, save state. **Event Detail**: ```typescript { componentId: string; // Unique component ID element: HTMLElement; // Component root element (about to be removed) component: Component; // Component instance } ``` **Example**: ```javascript window.addEventListener('livecomponent:unmounted', (event) => { const { componentId, element, component } = event.detail; // Save state to localStorage localStorage.setItem(`component-${componentId}`, JSON.stringify(component.props)); // Clear timers if (window.componentTimers[componentId]) { clearInterval(window.componentTimers[componentId]); delete window.componentTimers[componentId]; } // Destroy third-party instances if (window.chartInstances[componentId]) { window.chartInstances[componentId].destroy(); delete window.chartInstances[componentId]; } // Track unmount analytics analytics.track('component_unmounted', { component_type: componentId, lifetime_ms: Date.now() - parseInt(element.dataset.initialized) }); }); ``` **Common Use Cases**: - Save component state before removal - Clear timers and intervals - Remove global event listeners - Destroy third-party plugin instances - Cancel pending network requests - Track component lifetime analytics --- ## Action Events ### livecomponent:action-start **Fired**: When a LiveAction is triggered (before server request). **Use Case**: Show loading states, disable buttons, track action starts. **Event Detail**: ```typescript { componentId: string; // Component ID action: string; // Action name params: object; // Action parameters element: HTMLElement; // Element that triggered action timestamp: number; // Start timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:action-start', (event) => { const { componentId, action, params, element } = event.detail; // Show loading state element.classList.add('loading'); element.disabled = true; // Show loading spinner for long-running actions if (['generateReport', 'processPayment'].includes(action)) { showLoadingSpinner(`Processing ${action}...`); } // Track action starts analytics.track('action_started', { component: componentId, action: action, params: params }); }); ``` **Common Use Cases**: - Show loading indicators - Disable form buttons to prevent double-submission - Show progress overlays - Track action analytics - Optimistic UI updates --- ### livecomponent:action-executed **Fired**: After a LiveAction completes successfully. **Use Case**: Hide loading states, show success messages, track performance. **Event Detail**: ```typescript { componentId: string; // Component ID action: string; // Action name params: object; // Action parameters result: any; // Action result duration: number; // Execution time (ms) timestamp: number; // Completion timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:action-executed', (event) => { const { componentId, action, result, duration } = event.detail; // Hide loading states hideLoadingSpinner(); // Show success message for specific actions if (action === 'saveForm') { showToast('Changes saved successfully!', 'success'); } // Track slow actions if (duration > 1000) { console.warn(`Slow action: ${action} took ${duration}ms`); analytics.track('slow_action', { component: componentId, action: action, duration: duration }); } // Update badge counts if (action === 'markAsRead') { const badge = document.querySelector('.unread-badge'); const count = parseInt(badge.textContent) - 1; badge.textContent = count > 0 ? count : ''; } }); ``` **Common Use Cases**: - Hide loading indicators - Show success notifications - Update UI elements outside component - Track action performance - Trigger follow-up actions --- ### livecomponent:action-failed **Fired**: When a LiveAction fails (validation, authorization, or server error). **Use Case**: Show error messages, re-enable forms, track failures. **Event Detail**: ```typescript { componentId: string; // Component ID action: string; // Action name params: object; // Action parameters error: { message: string; // Error message code: string; // Error code details: object; // Additional error details }; duration: number; // Time before failure (ms) timestamp: number; // Failure timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:action-failed', (event) => { const { componentId, action, error, element } = event.detail; // Re-enable form elements if (element) { element.classList.remove('loading'); element.disabled = false; } // Show error message if (error.code === 'VALIDATION_ERROR') { showValidationErrors(error.details); } else if (error.code === 'RATE_LIMIT_EXCEEDED') { showToast(`Too many requests. Please wait ${error.details.retryAfter} seconds.`, 'warning'); } else { showToast(error.message || 'An error occurred', 'error'); } // Track failures analytics.track('action_failed', { component: componentId, action: action, error_code: error.code, error_message: error.message }); }); ``` **Common Use Cases**: - Display error messages - Re-enable disabled form elements - Show validation errors - Track error rates - Trigger error recovery flows --- ## State Events ### livecomponent:state-changed **Fired**: When component state changes (any LiveProp update). **Use Case**: React to any state change, sync with external systems. **Event Detail**: ```typescript { componentId: string; // Component ID previousState: object; // State before change currentState: object; // State after change changedProps: string[]; // Names of changed props } ``` **Example**: ```javascript window.addEventListener('livecomponent:state-changed', (event) => { const { componentId, previousState, currentState, changedProps } = event.detail; // Sync with localStorage localStorage.setItem(`state-${componentId}`, JSON.stringify(currentState)); // Track state changes changedProps.forEach(prop => { console.log(`${prop} changed:`, previousState[prop], '→', currentState[prop]); }); // Trigger side effects if (changedProps.includes('selectedItems')) { updateBulkActionBar(currentState.selectedItems); } }); ``` **Common Use Cases**: - Persist state to localStorage - Sync with external state management - Update derived UI elements - Track state change analytics - Debug state changes --- ### livecomponent:prop-updated **Fired**: When a specific LiveProp is updated. **Use Case**: React to specific property changes, trigger dependent updates. **Event Detail**: ```typescript { componentId: string; // Component ID propName: string; // Updated property name previousValue: any; // Value before update currentValue: any; // Value after update source: 'server' | 'client' | 'optimistic'; // Update source } ``` **Example**: ```javascript window.addEventListener('livecomponent:prop-updated', (event) => { const { componentId, propName, previousValue, currentValue, source } = event.detail; // React to specific prop changes if (propName === 'theme') { document.body.classList.remove(`theme-${previousValue}`); document.body.classList.add(`theme-${currentValue}`); localStorage.setItem('theme', currentValue); } if (propName === 'count' && source === 'optimistic') { // Animate optimistic updates animateNumberChange(previousValue, currentValue); } if (propName === 'sortOrder') { // Re-sort table when sort order changes sortTable(currentValue); } }); ``` **Common Use Cases**: - Trigger animations on specific prop changes - Update global UI state (theme, locale) - Sync specific props with localStorage - Track important prop changes - Trigger dependent updates --- ## Network Events ### livecomponent:request-start **Fired**: When a network request starts (action or state sync). **Use Case**: Show global loading indicators, track request starts. **Event Detail**: ```typescript { componentId: string; // Component ID requestId: string; // Unique request ID type: 'action' | 'sync'; // Request type payload: object; // Request payload timestamp: number; // Start timestamp } ``` **Example**: ```javascript let activeRequests = 0; window.addEventListener('livecomponent:request-start', (event) => { const { componentId, requestId, type } = event.detail; activeRequests++; // Show global loading indicator if (activeRequests === 1) { document.body.classList.add('loading'); } // Track request console.log(`Request ${requestId} started for ${componentId} (type: ${type})`); }); ``` --- ### livecomponent:request-complete **Fired**: When a network request completes successfully. **Use Case**: Hide loading indicators, track request performance. **Event Detail**: ```typescript { componentId: string; // Component ID requestId: string; // Unique request ID type: 'action' | 'sync'; // Request type response: object; // Server response duration: number; // Request duration (ms) timestamp: number; // Completion timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:request-complete', (event) => { const { componentId, requestId, duration } = event.detail; activeRequests--; // Hide global loading indicator if (activeRequests === 0) { document.body.classList.remove('loading'); } // Track slow requests if (duration > 1000) { console.warn(`Slow request: ${requestId} took ${duration}ms`); } }); ``` --- ### livecomponent:request-failed **Fired**: When a network request fails. **Use Case**: Handle network errors, retry logic, track failures. **Event Detail**: ```typescript { componentId: string; // Component ID requestId: string; // Unique request ID type: 'action' | 'sync'; // Request type error: { message: string; // Error message code: string; // Error code status: number; // HTTP status code }; duration: number; // Time before failure (ms) timestamp: number; // Failure timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:request-failed', (event) => { const { componentId, requestId, error, duration } = event.detail; activeRequests--; // Hide global loading indicator if (activeRequests === 0) { document.body.classList.remove('loading'); } // Handle network errors if (error.status === 0) { showToast('Network error. Please check your connection.', 'error'); } else if (error.status === 429) { showToast('Too many requests. Please slow down.', 'warning'); } else if (error.status >= 500) { showToast('Server error. Please try again later.', 'error'); } // Track failures analytics.track('request_failed', { component: componentId, request_id: requestId, error_code: error.code, status: error.status }); }); ``` --- ### livecomponent:batch-dispatched **Fired**: When batched requests are sent to server. **Use Case**: Track batching efficiency, debug batch behavior. **Event Detail**: ```typescript { batchId: string; // Unique batch ID actions: Array<{ // Batched actions componentId: string; action: string; params: object; }>; batchSize: number; // Number of actions in batch timestamp: number; // Dispatch timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:batch-dispatched', (event) => { const { batchId, actions, batchSize } = event.detail; console.log(`Batch ${batchId} dispatched with ${batchSize} actions:`, actions); // Track batching efficiency analytics.track('batch_dispatched', { batch_id: batchId, batch_size: batchSize, components: [...new Set(actions.map(a => a.componentId))] }); }); ``` --- ## Rendering Events ### livecomponent:render-start **Fired**: Before component re-rendering begins. **Use Case**: Save scroll position, prepare for render. **Event Detail**: ```typescript { componentId: string; // Component ID fragments: string[]; // Fragments to be rendered timestamp: number; // Render start timestamp } ``` **Example**: ```javascript const scrollPositions = new Map(); window.addEventListener('livecomponent:render-start', (event) => { const { componentId, fragments } = event.detail; // Save scroll positions of fragments fragments.forEach(fragmentName => { const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`); if (fragment) { scrollPositions.set(fragmentName, { scrollTop: fragment.scrollTop, scrollLeft: fragment.scrollLeft }); } }); }); ``` --- ### livecomponent:render-complete **Fired**: After component re-rendering completes. **Use Case**: Restore scroll position, re-initialize JavaScript. **Event Detail**: ```typescript { componentId: string; // Component ID fragments: string[]; // Rendered fragments duration: number; // Render duration (ms) timestamp: number; // Render complete timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:render-complete', (event) => { const { componentId, fragments, duration } = event.detail; // Restore scroll positions fragments.forEach(fragmentName => { const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`); const savedPosition = scrollPositions.get(fragmentName); if (fragment && savedPosition) { fragment.scrollTop = savedPosition.scrollTop; fragment.scrollLeft = savedPosition.scrollLeft; } }); // Re-initialize JavaScript in rendered fragments fragments.forEach(fragmentName => { const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`); initializeFragment(fragment); }); // Track slow renders if (duration > 100) { console.warn(`Slow render: ${componentId} took ${duration}ms`); } }); ``` --- ### livecomponent:fragment-updated **Fired**: When a specific fragment is updated. **Use Case**: Re-initialize fragment-specific JavaScript, track updates. **Event Detail**: ```typescript { componentId: string; // Component ID fragmentName: string; // Updated fragment name element: HTMLElement; // Fragment element timestamp: number; // Update timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:fragment-updated', (event) => { const { componentId, fragmentName, element } = event.detail; // Re-initialize specific fragment behaviors if (fragmentName === 'chart-container') { const chartEl = element.querySelector('.chart'); if (chartEl) { initializeChart(chartEl); } } if (fragmentName === 'image-gallery') { const images = element.querySelectorAll('img[data-lazy]'); lazyLoadImages(images); } // Track fragment updates analytics.track('fragment_updated', { component: componentId, fragment: fragmentName }); }); ``` --- ## SSE Events ### livecomponent:sse-connected **Fired**: When SSE connection is established. **Use Case**: Show online status, enable real-time features. **Event Detail**: ```typescript { componentId: string; // Component ID url: string; // SSE endpoint URL timestamp: number; // Connection timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:sse-connected', (event) => { const { componentId, url } = event.detail; console.log(`SSE connected for ${componentId} at ${url}`); // Show online indicator const indicator = document.querySelector('.connection-status'); indicator.classList.remove('offline'); indicator.classList.add('online'); indicator.textContent = 'Connected'; }); ``` --- ### livecomponent:sse-disconnected **Fired**: When SSE connection is lost. **Use Case**: Show offline status, disable real-time features. **Event Detail**: ```typescript { componentId: string; // Component ID reason: string; // Disconnection reason willReconnect: boolean; // Auto-reconnect enabled timestamp: number; // Disconnection timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:sse-disconnected', (event) => { const { componentId, reason, willReconnect } = event.detail; console.warn(`SSE disconnected for ${componentId}: ${reason}`); // Show offline indicator const indicator = document.querySelector('.connection-status'); indicator.classList.remove('online'); indicator.classList.add('offline'); indicator.textContent = willReconnect ? 'Reconnecting...' : 'Offline'; }); ``` --- ### livecomponent:sse-message **Fired**: When an SSE message is received. **Use Case**: Handle real-time updates, show notifications. **Event Detail**: ```typescript { componentId: string; // Component ID event: string; // SSE event type data: any; // Message data timestamp: number; // Message timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:sse-message', (event) => { const { componentId, event: eventType, data } = event.detail; if (eventType === 'notification') { showNotification(data.title, data.body); } if (eventType === 'user-joined') { updateOnlineUsers(data.userId, data.userName); } if (eventType === 'price-update') { animatePriceChange(data.symbol, data.price); } }); ``` --- ### livecomponent:sse-error **Fired**: When an SSE error occurs. **Use Case**: Handle SSE errors, implement fallback. **Event Detail**: ```typescript { componentId: string; // Component ID error: { message: string; // Error message code: string; // Error code }; timestamp: number; // Error timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:sse-error', (event) => { const { componentId, error } = event.detail; console.error(`SSE error for ${componentId}:`, error.message); // Fallback to polling if (!window.pollingTimers[componentId]) { window.pollingTimers[componentId] = setInterval(() => { LiveComponent.getComponent(componentId).executeAction('refresh'); }, 5000); } }); ``` --- ## Upload Events ### livecomponent:upload-start **Fired**: When file upload starts. **Use Case**: Show upload progress UI. **Event Detail**: ```typescript { componentId: string; // Component ID uploadId: string; // Unique upload ID file: { name: string; size: number; type: string; }; chunks: number; // Total number of chunks timestamp: number; // Upload start timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:upload-start', (event) => { const { uploadId, file, chunks } = event.detail; // Create progress UI const progressEl = document.createElement('div'); progressEl.id = `upload-${uploadId}`; progressEl.innerHTML = `
${file.name}
Uploading (0/${chunks} chunks)...
`; document.querySelector('.upload-queue').appendChild(progressEl); }); ``` --- ### livecomponent:upload-progress **Fired**: During file upload (per chunk). **Use Case**: Update progress bar, show upload speed. **Event Detail**: ```typescript { componentId: string; // Component ID uploadId: string; // Upload ID progress: { loaded: number; // Bytes uploaded total: number; // Total bytes percentage: number; // Progress percentage chunk: number; // Current chunk totalChunks: number; // Total chunks }; timestamp: number; // Progress update timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:upload-progress', (event) => { const { uploadId, progress } = event.detail; // Update progress bar const progressEl = document.querySelector(`#upload-${uploadId}`); const progressBar = progressEl.querySelector('.progress'); const statusEl = progressEl.querySelector('.status'); progressBar.style.width = `${progress.percentage}%`; statusEl.textContent = `Uploading (${progress.chunk}/${progress.totalChunks} chunks) - ${progress.percentage}%`; }); ``` --- ### livecomponent:upload-complete **Fired**: When file upload completes successfully. **Use Case**: Show success message, clean up progress UI. **Event Detail**: ```typescript { componentId: string; // Component ID uploadId: string; // Upload ID file: { name: string; size: number; url: string; // Uploaded file URL }; duration: number; // Upload duration (ms) timestamp: number; // Completion timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:upload-complete', (event) => { const { uploadId, file, duration } = event.detail; // Update progress UI const progressEl = document.querySelector(`#upload-${uploadId}`); const statusEl = progressEl.querySelector('.status'); statusEl.textContent = `✓ Complete (${(duration / 1000).toFixed(1)}s)`; statusEl.classList.add('success'); // Remove after 3 seconds setTimeout(() => progressEl.remove(), 3000); // Show success toast showToast(`${file.name} uploaded successfully!`, 'success'); }); ``` --- ### livecomponent:upload-failed **Fired**: When file upload fails. **Use Case**: Show error message, allow retry. **Event Detail**: ```typescript { componentId: string; // Component ID uploadId: string; // Upload ID file: { name: string; size: number; }; error: { message: string; // Error message code: string; // Error code chunk: number; // Failed chunk (if applicable) }; timestamp: number; // Failure timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:upload-failed', (event) => { const { uploadId, file, error } = event.detail; // Update progress UI const progressEl = document.querySelector(`#upload-${uploadId}`); const statusEl = progressEl.querySelector('.status'); statusEl.textContent = `✗ Failed: ${error.message}`; statusEl.classList.add('error'); // Add retry button const retryBtn = document.createElement('button'); retryBtn.textContent = 'Retry'; retryBtn.onclick = () => retryUpload(uploadId); progressEl.appendChild(retryBtn); // Show error toast showToast(`Failed to upload ${file.name}: ${error.message}`, 'error'); }); ``` --- ## Error Events ### livecomponent:error **Fired**: When a general LiveComponent error occurs. **Use Case**: Global error handling, error tracking. **Event Detail**: ```typescript { componentId: string; // Component ID error: { message: string; // Error message code: string; // Error code stack: string; // Stack trace }; context: string; // Error context ('render', 'action', 'network', etc.) timestamp: number; // Error timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:error', (event) => { const { componentId, error, context } = event.detail; console.error(`LiveComponent error in ${componentId} (${context}):`, error); // Send to error tracking service if (window.errorTracker) { window.errorTracker.captureException(error, { component: componentId, context: context }); } // Show generic error message to user showToast('An unexpected error occurred. Please refresh the page.', 'error'); }); ``` --- ### livecomponent:validation-error **Fired**: When server-side validation fails. **Use Case**: Display validation errors, focus first error. **Event Detail**: ```typescript { componentId: string; // Component ID action: string; // Action that failed validation errors: Record; // Validation errors by field timestamp: number; // Error timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:validation-error', (event) => { const { componentId, action, errors } = event.detail; // Display errors next to fields Object.entries(errors).forEach(([field, messages]) => { const input = document.querySelector(`[name="${field}"]`); const errorEl = input.parentElement.querySelector('.error-message'); if (errorEl) { errorEl.textContent = messages[0]; errorEl.style.display = 'block'; } input.classList.add('invalid'); }); // Focus first invalid field const firstInvalid = document.querySelector('.invalid'); if (firstInvalid) { firstInvalid.focus(); } }); ``` --- ### livecomponent:rate-limit-exceeded **Fired**: When rate limit is exceeded for an action. **Use Case**: Show rate limit message, display countdown. **Event Detail**: ```typescript { componentId: string; // Component ID action: string; // Rate-limited action retryAfter: number; // Seconds until retry allowed timestamp: number; // Rate limit timestamp } ``` **Example**: ```javascript window.addEventListener('livecomponent:rate-limit-exceeded', (event) => { const { componentId, action, retryAfter } = event.detail; // Show countdown toast let remaining = retryAfter; const toastId = showToast( `Rate limit exceeded. Please wait ${remaining} seconds.`, 'warning', { duration: retryAfter * 1000 } ); const countdown = setInterval(() => { remaining--; if (remaining > 0) { updateToast(toastId, `Rate limit exceeded. Please wait ${remaining} seconds.`); } else { clearInterval(countdown); updateToast(toastId, 'You can try again now!'); } }, 1000); }); ``` --- ## Event Patterns ### Event Delegation ```javascript // Instead of listening on each component document.addEventListener('livecomponent:action-executed', (event) => { if (event.detail.componentId.startsWith('notification-')) { updateNotificationBadge(); } }); ``` ### Debounced Event Handlers ```javascript import { debounce } from './utils'; window.addEventListener('livecomponent:prop-updated', debounce((event) => { if (event.detail.propName === 'searchQuery') { updateSearchResults(event.detail.currentValue); } }, 300) ); ``` ### Global Loading Indicator ```javascript let activeRequests = 0; window.addEventListener('livecomponent:request-start', () => { activeRequests++; if (activeRequests === 1) { document.body.classList.add('loading'); } }); window.addEventListener('livecomponent:request-complete', () => { activeRequests--; if (activeRequests === 0) { document.body.classList.remove('loading'); } }); window.addEventListener('livecomponent:request-failed', () => { activeRequests--; if (activeRequests === 0) { document.body.classList.remove('loading'); } }); ``` ### Performance Monitoring ```javascript window.addEventListener('livecomponent:action-executed', (event) => { const { action, duration } = event.detail; if (duration > 1000) { console.warn(`Slow action: ${action} (${duration}ms)`); } // Track to analytics analytics.track('action_performance', { action: action, duration: duration }); }); ``` --- ## Best Practices 1. **Use Event Delegation**: Listen on `window` instead of individual elements 2. **Cleanup Listeners**: Remove listeners in `unmounted` event 3. **Debounce/Throttle**: Use debouncing for high-frequency events 4. **Error Handling**: Always handle errors gracefully 5. **Performance**: Minimize work in event handlers 6. **Analytics**: Track important events for monitoring 7. **User Feedback**: Provide visual feedback for all events 8. **Accessibility**: Announce important events to screen readers --- For more examples and patterns, see: - [Advanced Features Guide](advanced-features.md) - [Troubleshooting Guide](troubleshooting.md) - [API Reference](api-reference.md)