@tabbridge/use-multi-tab-detection
v2.0.0
Published
React hook for detecting when your application is open in multiple browser tabs
Maintainers
Readme
@tabbridge/use-multi-tab-detection
A lightweight React hook for detecting when your application is open in multiple browser tabs.
Features
- ✅ Native
BroadcastChannelAPI for efficient inter-tab communication - ✅ Fully type-safe with strict TypeScript
- ✅ Automatic cleanup of inactive tabs
- ✅ Configurable heartbeat intervals and thresholds
- ✅ Callback support for state changes
- ✅ Browser support detection
- ✅ Zero dependencies (except React peer dependency)
Installation
npm install @tabbridge/use-multi-tab-detectionQuick Start
import { useMultiTabDetection } from '@tabbridge/use-multi-tab-detection';
function App() {
const { isMultiTab, tabCount } = useMultiTabDetection({
channelName: 'my-app'
});
if (isMultiTab) {
return <div>⚠️ Warning: This app is open in {tabCount} tabs</div>;
}
return <div>Your app content</div>;
}Usage
Basic Implementation
Add the hook to your root layout or any component where you want to track multiple tabs:
import { useMultiTabDetection } from '@tabbridge/use-multi-tab-detection';
export default function Layout() {
const { isMultiTab, tabCount, tabId } = useMultiTabDetection({
channelName: 'my-app-channel',
debug: process.env.NODE_ENV === 'development'
});
return (
<div>
{isMultiTab && (
<div className='warning-banner'>
Multiple tabs detected ({tabCount} active)
</div>
)}
{/* Your app content */}
</div>
);
}With Callbacks
React to multi-tab state changes with the onMultiTabChange callback:
const { isMultiTab, tabCount, activeTabUrls } = useMultiTabDetection({
channelName: 'my-app',
onMultiTabChange: (isMultiTab, count, urls) => {
console.log(`Multi-tab: ${isMultiTab}, Count: ${count}`);
console.log('Active URLs:', Array.from(urls.values()));
// Send analytics event
if (isMultiTab) {
analytics.track('multi_tab_detected', { tabCount: count });
}
}
});API Reference
useMultiTabDetection(options?)
Options
| Option | Type | Default | Description |
| --------------------- | ---------- | ------------ | ------------------------------------------- |
| channelName | string | Required | Unique identifier for the BroadcastChannel |
| heartbeatInterval | number | 10000 | Interval (ms) between heartbeat messages |
| inactivityThreshold | number | 30000 | Time (ms) before considering a tab inactive |
| debug | boolean | false | Enable console logging for debugging |
| onMultiTabChange | function | undefined | Callback when multi-tab state changes |
onMultiTabChange Callback
(isMultiTab: boolean, tabCount: number, activeTabUrls: Map<string, string>) => voidReturn Value
| Property | Type | Description |
| --------------- | --------------------- | -------------------------------------------- |
| isMultiTab | boolean | Whether multiple tabs are currently detected |
| tabCount | number | Total count of active tabs |
| tabId | string | Unique identifier for the current tab |
| isSupported | boolean | Whether BroadcastChannel API is supported |
| activeTabUrls | Map<string, string> | Map of tab IDs to their URLs |
How It Works
- Tab Registration: Each tab creates a unique ID and joins a
BroadcastChannel - Heartbeat System: Tabs send periodic heartbeat messages to announce they're active
- Tab Discovery: New tabs broadcast a discovery message, and existing tabs respond
- Inactive Cleanup: Tabs that haven't sent a heartbeat within the threshold are removed
- State Updates: The hook tracks active tabs and updates state accordingly
Tab 1 Tab 2
| |
|--[heartbeat]---------->|
|<-------[heartbeat]-----|
| |
|--[heartbeat]---------->|
| X (closed)
|
|--[cleanup after 30s]-->
| (detects Tab 2 inactive)Browser Support
Uses the BroadcastChannel API:
- ✅ Chrome 54+
- ✅ Firefox 38+
- ✅ Safari 15.4+
- ✅ Edge 79+
- ❌ Internet Explorer (not supported)
The hook gracefully handles unsupported browsers by setting isSupported: false.
Examples
Analytics Tracking
useMultiTabDetection({
channelName: 'reporting-demo',
onMultiTabChange: async (isMultiTab, count, urls) => {
if (isMultiTab) {
await fetch('/api/report-multi-tab', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tabCount: count,
urls: Array.from(urls.values())
})
});
}
}
});Custom Warning Banner
function WarningBanner() {
const { isMultiTab, tabCount } = useMultiTabDetection({
channelName: 'banner-demo'
});
if (!isMultiTab) return null;
return (
<div className='bg-yellow-100 border-l-4 border-yellow-500 p-4'>
<p className='font-bold'>⚠️ Multiple Tabs Detected</p>
<p>
You have {tabCount} tabs open. Please close extra tabs to avoid
synchronization issues.
</p>
</div>
);
}Testing
Manual Testing
- Open your app in one tab with
debug: trueenabled - Open the same page in a second tab
- Check the browser console for debug messages
- Verify
isMultiTabbecomestrueandtabCountshows2 - Close one tab and verify the count decreases after the inactivity threshold
Troubleshooting
Hook not detecting other tabs
- Ensure all tabs use the same
channelName - Verify
isSupportedistruein your browser - Enable
debug: trueto see message flow in console - Check browser console for errors
False positives (detecting closed tabs)
- Increase
inactivityThresholdif you have slow network conditions - Default 30s threshold should work for most cases
- Verify cleanup is running with
debug: true
Performance concerns
- Default settings are already optimized for most use cases
- Increase
heartbeatIntervalto reduce message frequency if needed - Avoid triggering expensive operations in
onMultiTabChangecallback - Consider debouncing state updates that cause re-renders
Performance Notes
⚠️ Important: While BroadcastChannel is lightweight, be mindful of:
- Triggering global context updates on every state change
- Re-rendering expensive components unnecessarily
- Triggering data refetches in the callback
Best practices:
- Use
onMultiTabChangefor side effects (analytics, logging) - Memoize expensive computations based on
isMultiTab - Debounce UI updates if needed
Security Considerations
- BroadcastChannel only communicates within the same origin (browser security)
- Messages cannot be accessed across different domains
- Tab IDs are randomly generated and not tied to user identity
- Avoid sending sensitive data through the channel
Why BroadcastChannel?
We chose BroadcastChannel over alternatives for several reasons:
| Approach | Pros | Cons | | ----------------------- | ------------------------------------------------- | ------------------------------------------ | | BroadcastChannel | ✅ Native API✅ No polling✅ Auto cleanup | ❌ Limited browser support | | LocalStorage events | ✅ Wider support | ❌ Same-origin only❌ No auto cleanup | | SharedWorker | ✅ Powerful | ❌ Complex setup❌ Limited support | | Server polling | ✅ Works everywhere | ❌ Network overhead❌ Requires backend |
TypeScript
This package is written in TypeScript and includes full type definitions:
interface UseMultiTabDetectionOptions {
channelName: string;
heartbeatInterval?: number;
inactivityThreshold?: number;
debug?: boolean;
onMultiTabChange?: (
isMultiTab: boolean,
tabCount: number,
activeTabUrls: Map<string, string>
) => void;
}
interface UseMultiTabDetectionResult {
isMultiTab: boolean;
tabCount: number;
tabId: string;
isSupported: boolean;
activeTabUrls: Map<string, string>;
}Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © TabBridge https://github.com/tabbridge
Links
Made with ❤️ by the TabBridge team
