- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
35 KiB
LiveComponents Events Reference
Complete reference for all client-side events and lifecycle hooks with examples and best practices.
Table of Contents
- Lifecycle Events
- Action Events
- State Events
- Network Events
- Rendering Events
- SSE Events
- Upload Events
- Error Events
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:
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element
props: object; // Initial component props
}
Example:
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:
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element
component: Component; // Component instance
props: object; // Current component props
}
Example:
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:
{
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:
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:
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element (about to be removed)
component: Component; // Component instance
}
Example:
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:
{
componentId: string; // Component ID
action: string; // Action name
params: object; // Action parameters
element: HTMLElement; // Element that triggered action
timestamp: number; // Start timestamp
}
Example:
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:
{
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:
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:
{
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:
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:
{
componentId: string; // Component ID
previousState: object; // State before change
currentState: object; // State after change
changedProps: string[]; // Names of changed props
}
Example:
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:
{
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:
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:
{
componentId: string; // Component ID
requestId: string; // Unique request ID
type: 'action' | 'sync'; // Request type
payload: object; // Request payload
timestamp: number; // Start timestamp
}
Example:
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:
{
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:
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:
{
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:
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:
{
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:
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:
{
componentId: string; // Component ID
fragments: string[]; // Fragments to be rendered
timestamp: number; // Render start timestamp
}
Example:
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:
{
componentId: string; // Component ID
fragments: string[]; // Rendered fragments
duration: number; // Render duration (ms)
timestamp: number; // Render complete timestamp
}
Example:
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:
{
componentId: string; // Component ID
fragmentName: string; // Updated fragment name
element: HTMLElement; // Fragment element
timestamp: number; // Update timestamp
}
Example:
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:
{
componentId: string; // Component ID
url: string; // SSE endpoint URL
timestamp: number; // Connection timestamp
}
Example:
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:
{
componentId: string; // Component ID
reason: string; // Disconnection reason
willReconnect: boolean; // Auto-reconnect enabled
timestamp: number; // Disconnection timestamp
}
Example:
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:
{
componentId: string; // Component ID
event: string; // SSE event type
data: any; // Message data
timestamp: number; // Message timestamp
}
Example:
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:
{
componentId: string; // Component ID
error: {
message: string; // Error message
code: string; // Error code
};
timestamp: number; // Error timestamp
}
Example:
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:
{
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:
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 = `
<div class="upload-item">
<span class="filename">${file.name}</span>
<div class="progress-bar">
<div class="progress" style="width: 0%"></div>
</div>
<span class="status">Uploading (0/${chunks} chunks)...</span>
</div>
`;
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:
{
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:
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:
{
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:
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:
{
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:
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:
{
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:
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:
{
componentId: string; // Component ID
action: string; // Action that failed validation
errors: Record<string, string[]>; // Validation errors by field
timestamp: number; // Error timestamp
}
Example:
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:
{
componentId: string; // Component ID
action: string; // Rate-limited action
retryAfter: number; // Seconds until retry allowed
timestamp: number; // Rate limit timestamp
}
Example:
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
// Instead of listening on each component
document.addEventListener('livecomponent:action-executed', (event) => {
if (event.detail.componentId.startsWith('notification-')) {
updateNotificationBadge();
}
});
Debounced Event Handlers
import { debounce } from './utils';
window.addEventListener('livecomponent:prop-updated',
debounce((event) => {
if (event.detail.propName === 'searchQuery') {
updateSearchResults(event.detail.currentValue);
}
}, 300)
);
Global Loading Indicator
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
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
- Use Event Delegation: Listen on
windowinstead of individual elements - Cleanup Listeners: Remove listeners in
unmountedevent - Debounce/Throttle: Use debouncing for high-frequency events
- Error Handling: Always handle errors gracefully
- Performance: Minimize work in event handlers
- Analytics: Track important events for monitoring
- User Feedback: Provide visual feedback for all events
- Accessibility: Announce important events to screen readers
For more examples and patterns, see: