Event Bus
The Event Bus enables different parts of your application to communicate and stay synchronized through message passing. It supports event-driven architecture for drop-ins, allowing containers to react to changes from other containers and communicate data changes to the storefront.
Import
From drop-in project using the SDK:
import { events } from '@adobe-commerce/elsie/lib';
From integration project (storefront):
import { events } from '@dropins/tools/event-bus.js';
Core Methods
Subscribe to Events
Subscribe to events and receive notifications when they occur.
const eventListener = events.on('<event>', (payload) => { // Handle the event payload console.log('Event received:', payload);});
// Stop listening to the eventeventListener.off();
Example:
// Listen for cart updatesconst cartListener = events.on('cart/data', (cartData) => { if (cartData) { console.log(`Cart has ${cartData.totalQuantity} items`); updateCartUI(cartData); } else { console.log('Cart is empty'); showEmptyCart(); }});
// Later, when you want to stop listeningcartListener.off();
Emit Events
Broadcast events to all listeners across your application.
events.emit('<event>', payload);
Examples:
// Emit cart dataconst cartData = { id: 'cart-123', totalQuantity: 2, items: [ { uid: 'item-1', quantity: 1, sku: 'PROD-001', name: 'Product Name' } ]};
events.emit('cart/data', cartData);
Get Last Event Payload
Retrieve the most recent payload for a specific event.
const lastPayload = events.lastPayload('<event>');
Example:
// Get the current cart state without waiting for an eventconst currentCart = events.lastPayload('cart/data');
if (currentCart) { console.log('Current cart total:', currentCart.totalQuantity);}
Enable Debug Logging
Enable console logging to debug event flow.
// Enable logging to see all events in consoleevents.enableLogger(true);
Advanced Features
Eager Loading
When subscribing to events, you can execute the event handler immediately with the last known payload. This is useful for getting the current state without waiting for the next event.
// Handler will execute immediately if there's a previous payloadconst listener = events.on('cart/data', (cartData) => { console.log('Cart data received:', cartData);}, { eager: true });
Use cases:
- Initialize UI components with current state
- Avoid waiting for the first event emission
- Ensure components have the latest data on mount
Event Scoping
Create namespaced events to avoid conflicts between different parts of your application.
// Subscribe to a scoped eventconst scopedListener = events.on('data/update', (data) => { console.log('Scoped data received:', data);}, { scope: 'feature-a' });
// Emit a scoped eventevents.emit('data/update', payload, { scope: 'feature-a' });
// Get last payload for a scoped eventconst lastScopedData = events.lastPayload('data/update', { scope: 'feature-a' });
Scoped event names:
When using scopes, the actual event name becomes scope/event
. For example:
'feature-a/data/update'
instead of'data/update'
'module-b/user/action'
instead of'user/action'
Use cases:
- Separate different features or modules
- Different contexts within the same application
- Component-specific event handling
Combining Options
Use both eager loading and scoping together for powerful event handling.
// Subscribe to a scoped event with eager loadingconst listener = events.on('locale', (locale) => { console.log('Current locale:', locale);}, { eager: true, scope: 'user-preferences'});
Event-Driven Drop-ins
The Event Bus enables drop-ins to be truly event-driven, allowing for loose coupling between components and seamless communication across the application.
Container-to-Container Communication
Containers can react to changes from other Containers, enabling complex interactions without direct dependencies.
// Product Container: Emits when a product is added to cartfunction ProductContainer() { const handleAddToCart = (product) => { // Add to cart logic...
// Notify other containers about the cart change events.emit('cart/data', updatedCartData); };
return ( <button onClick={() => handleAddToCart(product)}> Add to Cart </button> );}
// Cart Container: Reacts to cart changes from any sourcefunction CartContainer() { useEffect(() => { const cartListener = events.on('cart/data', (cartData) => { updateCartDisplay(cartData); updateCartBadge(cartData.totalQuantity); }, { eager: true });
return () => cartListener.off(); }, []);
return <CartDisplay />;}
// Mini Cart Container: Also reacts to the same cart changesfunction MiniCartContainer() { useEffect(() => { const cartListener = events.on('cart/data', (cartData) => { updateMiniCart(cartData); }, { eager: true });
return () => cartListener.off(); }, []);
return <MiniCart />;}
Storefront Communication
Drop-ins can communicate data changes to the storefront, enabling seamless integration with the host application.
// Authentication Container: Notifies storefront of login/logoutfunction AuthContainer() { const handleLogin = (userData) => { // Login logic...
// Notify storefront of authentication change events.emit('authenticated', true); };
const handleLogout = () => { // Logout logic...
// Notify storefront of authentication change events.emit('authenticated', false); };
return <AuthForm onLogin={handleLogin} onLogout={handleLogout} />;}
// Storefront can listen for authentication changes// This would be in the host applicationconst authListener = events.on('authenticated', (isAuthenticated) => { if (isAuthenticated) { showUserMenu(); enableCheckout(); } else { hideUserMenu(); disableCheckout(); }}, { eager: true });
Best Practices
- Always unsubscribe from events when components unmount to prevent memory leaks
- Use scopes to organize events by feature or component
- Enable eager loading when you need immediate access to current state
- Use descriptive event names that clearly indicate what data they contain
- Handle null/undefined payloads gracefully in your event handlers
- Enable logging during development to debug event flow
- Keep event payloads lightweight to avoid performance issues
- Document your event contracts so other developers know what to expect