Skip to content

Event Listeners

StorageManager.js allows you to listen for changes to specific keys across different tabs or windows. This is useful for keeping your application synchronized when a user has multiple tabs open.

The event listener system is built on top of the browser’s native storage event, which fires when storage is modified in a different browsing context (tab or window) on the same domain.

Registers a callback function to be executed when a specific key changes in storage.

storage.onChange(key, callback)
  • key (String) — The key to listen for changes on
  • callback (Function) — A function called when the key changes. Receives:
    • newValue (Any) — The new value (parsed from JSON)
    • oldValue (Any) — The previous value (parsed from JSON)

void (synchronous method)

// In Tab 1: Register a listener
storage.onChange('userProfile', (newValue, oldValue) => {
console.log('User profile changed!');
console.log('Old value:', oldValue);
console.log('New value:', newValue);
// Update UI with new values
updateUI(newValue);
});
// In Tab 2: Change the value
await storage.set('userProfile', {
name: 'Bob',
theme: 'light'
});
// Tab 1's callback is triggered:
// "User profile changed!"
// "Old value: { name: 'Alice', theme: 'dark' }"
// "New value: { name: 'Bob', theme: 'light' }"
  • Synchronizing user preferences across tabs
  • Showing notifications when data changes in another tab
  • Updating a shopping cart when items are added/removed in another tab
  • Real-time collaboration features
  • Keeping dashboards in sync

Unregisters the change listener for a specific key.

storage.offChange(key)
  • key (String) — The key to stop listening for changes on

void (synchronous method)

// Register a listener
storage.onChange('notifications', (newValue) => {
showNotification(newValue);
});
// Later: Stop listening
storage.offChange('notifications');
// Changes to 'notifications' will no longer trigger the callback

Here’s a complete example of synchronizing state across multiple tabs:

const storage = new StorageManager(false, {
namespace: 'myApp'
});
// Initialize the UI with stored data
const cart = await storage.get('cart') || [];
renderCart(cart);
// Listen for cart changes from other tabs
storage.onChange('cart', (newCart) => {
console.log('Cart updated in another tab');
renderCart(newCart);
showNotification('Your cart was updated');
});
// Function to render cart items
function renderCart(items) {
const cartElement = document.getElementById('cart');
cartElement.innerHTML = items.map(item =>
`<div>${item.name} - $${item.price}</div>`
).join('');
}
function showNotification(message) {
// Show a toast/notification
console.log('Notification:', message);
}
const storage = new StorageManager(false, {
namespace: 'myApp'
});
// Add item to cart
async function addToCart(item) {
const cart = await storage.get('cart') || [];
cart.push(item);
await storage.set('cart', cart);
console.log('Item added to cart');
// Tab 1 will automatically be notified and update its UI
}
// User clicks "Add to Cart"
document.getElementById('addBtn').addEventListener('click', () => {
addToCart({
id: 123,
name: 'Widget',
price: 29.99
});
});

Sync user preferences across all open tabs:

const storage = new StorageManager(false, {
namespace: 'myApp'
});
// Initialize theme from storage
const savedTheme = await storage.get('theme') || 'light';
applyTheme(savedTheme);
// Listen for theme changes
storage.onChange('theme', (newTheme) => {
console.log(`Theme changed to ${newTheme} in another tab`);
applyTheme(newTheme);
});
// Theme toggle button
document.getElementById('themeToggle').addEventListener('click', async () => {
const currentTheme = await storage.get('theme') || 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
await storage.set('theme', newTheme);
applyTheme(newTheme);
// All other tabs will automatically update their theme
});
function applyTheme(theme) {
document.body.className = theme;
document.getElementById('themeToggle').textContent =
theme === 'light' ? '🌙 Dark Mode' : '☀️ Light Mode';
}
// Clean up listener when page unloads
window.addEventListener('beforeunload', () => {
storage.offChange('theme');
});

You can register listeners for multiple keys in the same instance:

// Listen for user profile changes
storage.onChange('userProfile', (newProfile) => {
updateUserDisplay(newProfile);
});
// Listen for settings changes
storage.onChange('settings', (newSettings) => {
applySettings(newSettings);
});
// Listen for notifications
storage.onChange('notifications', (notifications) => {
showNotifications(notifications);
});
// Later: Clean up all listeners
storage.offChange('userProfile');
storage.offChange('settings');
storage.offChange('notifications');

  1. Cross-tab only: The storage event only fires in tabs other than the one that made the change.
  2. Same origin: Tabs must be on the same domain/protocol/port.
  3. Browser support: All modern browsers support the storage event (IE8+).
  1. Clean up listeners: Always call offChange() when you’re done listening
  2. Handle null values: Check for null in callbacks (key might have been removed)
  3. Debounce rapid changes: If you expect frequent updates, debounce your callback
  4. Validate data: Always validate data in callbacks before using it
// Good: Clean up and validate
storage.onChange('user', (newUser) => {
if (!newUser) {
console.log('User logged out in another tab');
redirectToLogin();
return;
}
updateUserDisplay(newUser);
});
window.addEventListener('beforeunload', () => {
storage.offChange('user');
});

Explore practical Examples to see all these features working together in real applications.