Skip to content

Cross-Tab Sync

nanostores-storage can listen for storage changes from other browser tabs or windows. This enables real-time synchronization across your application.

Enabling Cross-Tab Sync

Option 1: Enable at Creation

import { createStorageStore, localStorageAdapter } from "@vp-tw/nanostores-storage";
const store = createStorageStore(localStorageAdapter, "shared-key", {
listen: true, // Start listening immediately
});

Option 2: Control Manually

const store = createStorageStore(localStorageAdapter, "shared-key");
// Start listening
store.listener.on();
// Check status
console.log(store.listener.$on.get()); // true
// Stop listening
store.listener.off();

How It Works

When listen is enabled:

  1. The adapter subscribes to StorageEvent on the window object
  2. When storage changes externally, the event fires
  3. The store re-reads from storage and updates its value
  4. Your subscribers are notified of the change
// Tab 1
const store = createStorageStore(localStorageAdapter, "counter", {
listen: true,
});
store.$value.subscribe((value) => {
console.log("Counter:", value); // Logs when Tab 2 changes it
});
// Tab 2
localStorage.setItem("counter", "42");
// Tab 1 automatically updates!

Listener State

The listener state is a reactive atom:

import { useStore } from "@nanostores/react";
function SyncIndicator() {
const isListening = useStore(store.listener.$on);
return (
<span>
{isListening ? "Syncing enabled" : "Syncing disabled"}
</span>
);
}

localStorage vs sessionStorage

FeaturelocalStoragesessionStorage
Cross-tab events✅ Yes❌ No*
PersistenceUntil clearedUntil tab closes

*sessionStorage is isolated per tab, so there are no cross-tab events to listen for.

Performance Considerations

  • Each listener adds a storage event handler to window
  • Event handlers are reference-counted and cleaned up automatically
  • Multiple stores can share the same underlying event listener
// These two stores share the same event listener internally
const store1 = createStorageStore(localStorageAdapter, "key1", { listen: true });
const store2 = createStorageStore(localStorageAdapter, "key2", { listen: true });

Example: Real-Time Theme Sync

theme-store.ts
import { createStorageStore, localStorageAdapter } from "@vp-tw/nanostores-storage";
export const themeStore = createStorageStore(localStorageAdapter, "theme", {
defaultValue: "system",
listen: true,
});
// Apply theme on change
themeStore.$value.subscribe((theme) => {
document.documentElement.dataset.theme = theme ?? "system";
});

Now when you change the theme in one tab, all other tabs update instantly.

Cleanup

When using manual control, remember to stop listening when appropriate:

// In a component
useEffect(() => {
store.listener.on();
return () => {
store.listener.off();
};
}, []);

With listen: true at creation, the listener runs for the lifetime of the store.

Next Steps