Gathering detailed insights and metrics for react-broadcast-sync
Gathering detailed insights and metrics for react-broadcast-sync
Gathering detailed insights and metrics for react-broadcast-sync
Gathering detailed insights and metrics for react-broadcast-sync
persist-and-sync
Zustand middleware to easily persist and sync Zustand state between tabs and windows
concent
Build-in dependency collection, a predictable、zero-cost-use、progressive、high performance's react develop framework
react-context-sync
A React library to sync Context values across browser tabs.
zustand-sync-tabs
Zustand middleware to easily sync Zustand state between tabs and windows
npm install react-broadcast-sync
Typescript
Module System
Min. Node Version
Node Version
NPM Version
TypeScript (96.72%)
JavaScript (3.2%)
Shell (0.08%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
9 Stars
74 Commits
1 Forks
1 Watchers
1 Branches
2 Contributors
Updated on Jul 03, 2025
Latest Version
1.5.1
Package Id
react-broadcast-sync@1.5.1
Unpacked Size
1.78 MB
Size
1.29 MB
File Count
58
NPM Version
10.9.2
Node Version
20.11.1
Published on
Jul 03, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
2
41
Easily sync UI state or user events across browser tabs in React apps — notifications, presence, forms, and more. This package provides a clean and type-safe abstraction over the native API, enabling efficient, scoped, and reliable cross-tab messaging.
BroadcastProvider
for context-based usageCheck out our live demo to see the library in action! The demo showcases three main features:
Counter Synchronization
Text Synchronization
Todo List
The demo is built with React 19, TypeScript, Material-UI, and Framer Motion. You can find the source code in the demo directory.
1npm install react-broadcast-sync 2# or 3yarn add react-broadcast-sync 4# or 5pnpm add react-broadcast-sync
1import { useBroadcastChannel } from 'react-broadcast-sync'; 2 3function MyComponent() { 4 const { messages, postMessage, clearReceivedMessages } = useBroadcastChannel('my-channel'); 5 6 const handleSend = () => { 7 postMessage('greeting', { text: 'Hello from another tab!' }); 8 }; 9 10 return ( 11 <> 12 <button onClick={handleSend}>Send</button> 13 {messages.map(msg => ( 14 <div key={msg.id}> 15 {msg.message.text} 16 <button onClick={() => clearReceivedMessages({ ids: [msg.id] })}>Clear</button> 17 </div> 18 ))} 19 </> 20 ); 21}
1const {
2 channelName,
3 messages,
4 sentMessages,
5 postMessage,
6 clearReceivedMessages,
7 clearSentMessages,
8 error,
9} = useBroadcastChannel('my-channel', {
10 sourceName: 'my-tab',
11 cleaningInterval: 2000,
12 keepLatestMessage: true,
13 registeredTypes: ['greeting', 'notification'],
14 namespace: 'my-app',
15 deduplicationTTL: 10 * 60 * 1000, // 10 minutes
16 cleanupDebounceMs: 500, // Debounce cleanup operations by 500ms
17});
1postMessage('notification', { text: 'This disappears in 5s' }, { expirationDuration: 5000 });
BroadcastProvider
You can wrap part of your app with BroadcastProvider
and use useBroadcastProvider()
to consume the channel context.
1import { BroadcastProvider, useBroadcastProvider } from 'react-broadcast-sync'; 2 3function App() { 4 return ( 5 <BroadcastProvider channelName="notifications"> 6 <NotificationBar /> 7 </BroadcastProvider> 8 ); 9} 10 11function NotificationBar() { 12 const { messages } = useBroadcastProvider(); 13 14 return ( 15 <div> 16 {messages.map(msg => ( 17 <p key={msg.id}>{msg.message.text}</p> 18 ))} 19 </div> 20 ); 21}
useBroadcastChannel
Hook1const { 2 channelName, 3 messages, 4 sentMessages, 5 postMessage, 6 clearReceivedMessages, 7 clearSentMessages, 8 error, 9} = useBroadcastChannel(channelName, options);
1interface BroadcastOptions { 2 sourceName?: string; // Custom name for the message source 3 cleaningInterval?: number; // Interval in ms for cleaning expired messages (default: 1000) 4 keepLatestMessage?: boolean; // Keep only the latest message (default: false) 5 registeredTypes?: string[]; // List of allowed message types 6 namespace?: string; // Channel namespace for isolation 7 deduplicationTTL?: number; // Time in ms to keep message IDs for deduplication (default: 5 minutes) 8 cleanupDebounceMs?: number; // Debounce time in ms for cleanup operations (default: 0) 9 batchingDelayMs?: number; // Delay in ms to batch outgoing messages (default: 20). If > 0, messages are batched and sent together. 10 excludedBatchMessageTypes?: string[]; // Message types to always send immediately, never batched (default: []). 11}
Option | Default Value | Description |
---|---|---|
sourceName | undefined | Auto-generated if not provided |
cleaningInterval | 1000 | 1 second between cleanup runs |
keepLatestMessage | false | Keep all messages by default |
registeredTypes | [] | Accept all message types by default |
namespace | '' | No namespace by default |
deduplicationTTL | 300000 | 5 minutes (5 _ 60 _ 1000 ms) |
cleanupDebounceMs | 0 | No debounce by default |
batchingDelayMs | 20 | Batch delay in ms (0 = off) |
excludedBatchMessageTypes | [] | Types never batched |
1interface BroadcastActions { 2 channelName: string; // The resolved channel name (includes namespace) 3 messages: BroadcastMessage[]; // Received messages 4 sentMessages: BroadcastMessage[]; // Messages sent by this instance 5 postMessage: (type: string, content: any, options?: SendMessageOptions) => void; 6 clearReceivedMessages: (opts?: { ids?: string[]; types?: string[]; sources?: string[] }) => void; 7 clearSentMessages: (opts?: { ids?: string[]; types?: string[]; sync?: boolean }) => void; 8 getLatestMessage: (opts?: { type?: string; source?: string }) => BroadcastMessage | null; 9 closeChannel: () => void; 10 error: string | null; // Current error state 11}
useBroadcastChannel(channelName, options?)
Returns an object with:
Property | Type | Description |
---|---|---|
channelName | string | The resolved channel name (includes namespace) |
messages | BroadcastMessage[] | All received messages from other tabs |
sentMessages | BroadcastMessage[] | Messages sent from this tab |
postMessage() | function | Send a message to all tabs |
clearReceivedMessages() | function | Clear received messages. No filters ⇒ clear all. With filters, a message is deleted only if it matches every provided filter (ids , types , sources ). Empty arrays act as wildcards. |
clearSentMessages() | function | Clear messages this tab sent (same matching rules). Pass sync: true to broadcast the clear to other tabs. |
getLatestMessage() | function | Get the latest message matching optional filters (type , source ). Returns the most recent message that matches, or null if none. |
ping(timeoutMs?) | function | Ping other tabs on the channel and collect their source names. timeoutMs (default: 300ms) controls how long to wait for responses before resolving. Returns a Promise of string array. |
isPingInProgress | boolean | true while a ping is active, otherwise false . |
closeChannel() | function | Explicitly closes the broadcast channel and removes event listeners. Safe to call multiple times. |
error | string | null | Any runtime error from the channel |
1// Clear everything we've received 2clearReceivedMessages(); 3 4// Clear all messages we sent and broadcast the clear to other tabs 5clearSentMessages({ sync: true }); 6 7// Clear by id or by type (OR inside each array) 8clearReceivedMessages({ ids: ['123'] }); // id match 9clearReceivedMessages({ types: ['alert', 'chat'] }); // type match 10 11// Combine filters (logical AND between filters) 12// Removes messages whose id is '123' AND type is 'alert' 13clearSentMessages({ ids: ['123'], types: ['alert'] });
1interface SendMessageOptions { 2 expirationDuration?: number; // TTL in ms 3 expirationDate?: number; // Exact expiry timestamp 4}
1interface BroadcastMessage { 2 id: string; 3 type: string; 4 message: any; 5 timestamp: number; 6 source: string; 7 expirationDate?: number; 8}
You can use getLatestMessage
to retrieve the most recent message received, optionally filtered by type
and/or source
. If no options are provided, it returns the latest message of any kind. If no message matches, it returns null
.
Signature:
1getLatestMessage(options?: { type?: string; source?: string }): BroadcastMessage | null
Examples:
1// Get the latest message of any type/source
2const latest = getLatestMessage();
3
4// Get the latest message of a specific type
5const latestAlert = getLatestMessage({ type: 'alert' });
6
7// Get the latest message from a specific source
8const latestFromTab = getLatestMessage({ source: 'tab-123' });
9
10// Get the latest message of a specific type from a specific source
11const latestInfoFromTab = getLatestMessage({ type: 'info', source: 'tab-123' });
12
13// Check if there are any messages of a type
14if (getLatestMessage({ type: 'notification' })) {
15 // ...
16}
Behavior:
null
.null
.useBroadcastChannel
provides a ping
method and an isPingInProgress
state for discovering active sources (tabs) on the same channel.
ping(timeoutMs?: number): Promise<string[]>
: Broadcasts a ping and collects responses from other tabs within the timeout. Returns an array of source names (excluding your own).isPingInProgress: boolean
: Indicates if a ping is currently in progress.Example:
1const { ping, isPingInProgress } = useBroadcastChannel('my-channel', { 2 sourceName: 'my-tab', 3}); 4 5// To discover other active sources: 6const activeSources = await ping(300); // e.g., ['tab-2', 'tab-3'] 7 8// To show loading state: 9if (isPingInProgress) { 10 // Show spinner or status 11}
You can use closeChannel
to explicitly close the underlying BroadcastChannel and remove all event listeners. This is useful if you want to clean up resources before the component unmounts, or to stop all cross-tab communication on demand. Note that the channel will automatically close and all event listeners will be removed when the component unmounts, so this method is mainly useful for manual cleanup.
Signature:
1closeChannel(): void
Example:
1const { closeChannel } = useBroadcastChannel('my-channel');
2
3// ... later, when you want to stop all communication:
4closeChannel();
Notes:
closeChannel
, the channel is closed and will not send or receive any more messages.closeChannel
multiple times (idempotent).namespace
to isolate functionality between different app modules.registeredTypes
to avoid processing unknown or irrelevant messages.error
state in UI or logs to detect channel failures.keepLatestMessage: true
if you only care about the most recent message (e.g. status updates).deduplicationTTL
based on your message frequency and importance.cleanupDebounceMs
when dealing with rapid message updates to prevent performance issues.1function NotificationSystem() { 2 const { messages, postMessage } = useBroadcastChannel('notifications', { 3 keepLatestMessage: true, 4 registeredTypes: ['alert', 'info', 'warning'], 5 deduplicationTTL: 60000, // 1 minute 6 }); 7 8 return ( 9 <div> 10 {messages.map(msg => ( 11 <Notification key={msg.id} type={msg.type} content={msg.message} /> 12 ))} 13 </div> 14 ); 15}
1function FormSync() {
2 const { messages, postMessage } = useBroadcastChannel('form-sync', {
3 namespace: 'my-form',
4 cleaningInterval: 5000,
5 });
6
7 const handleChange = (field: string, value: string) => {
8 postMessage('field-update', { field, value }, { expirationDuration: 300000 }); // 5 minutes
9 };
10
11 return <Form onChange={handleChange} />;
12}
1function TabStatus() {
2 const { postMessage } = useBroadcastChannel('tab-status', {
3 sourceName: 'main-tab',
4 keepLatestMessage: true,
5 });
6
7 useEffect(() => {
8 postMessage('tab-active', { timestamp: Date.now() });
9 return () => postMessage('tab-inactive', { timestamp: Date.now() });
10 }, []);
11
12 return null;
13}
keepLatestMessage: true
for high-frequency updatesexpirationDuration
for temporary messagesBatching allows you to group multiple outgoing messages and send them together in a single post to the BroadcastChannel. This can significantly reduce the number of cross-tab events, improve performance, and avoid flooding the channel when many messages are sent in rapid succession (e.g., during fast typing or bulk updates).
How it works:
postMessage
within the batching window are buffered and sent as a batch (array of messages) after the delay.Array.isArray(event.data)
.Why batching matters:
Example:
1const { postMessage } = useBroadcastChannel('my-channel', {
2 batchingDelayMs: 50, // Batch messages for up to 50ms
3 excludedBatchMessageTypes: ['alert'], // Always send 'alert' immediately
4});
5
6// These will be batched if sent within 50ms
7postMessage('edit', { field: 'a', value: 1 });
8postMessage('edit', { field: 'b', value: 2 });
9// This will be sent immediately
10postMessage('alert', { message: 'Something happened!' });
clearReceivedMessages
/ clearSentMessages
cleaningInterval
to automatically remove expired messagesThe deduplicationTTL
option creates a time window (in milliseconds) during which messages with the same content and type from the same source are considered duplicates and will be ignored. This is particularly useful for:
Recommended TTL values based on use case:
Example:
1// Without deduplication, this could cause a message loop
2function ChatComponent() {
3 const { postMessage } = useBroadcastChannel('chat', {
4 deduplicationTTL: 5000, // Ignore duplicate messages for 5 seconds
5 });
6
7 const handleMessage = (text: string) => {
8 postMessage('chat-message', { text });
9 };
10}
cleanupDebounceMs
to prevent excessive cleanup operationscleaningInterval
based on your message expiration needsMessages Not Received
registeredTypes
includes your message typededuplicationTTL
isn't too shortPerformance Issues
cleanupDebounceMs
if cleanup is too frequentkeepLatestMessage: true
for high-frequency updatescleaningInterval
if cleanup is too aggressiveMemory Leaks
Enable debug logging by setting the environment variable:
1REACT_APP_DEBUG_BROADCAST=true
This will log:
1import { renderHook, act } from '@testing-library/react-hooks'; 2import { useBroadcastChannel } from 'react-broadcast-sync'; 3 4test('should send and receive messages', () => { 5 const { result } = renderHook(() => useBroadcastChannel('test-channel')); 6 7 act(() => { 8 result.current.postMessage('test', { data: 'hello' }); 9 }); 10 11 expect(result.current.messages).toHaveLength(1); 12 expect(result.current.messages[0].message.data).toBe('hello'); 13});
1import { render, screen } from '@testing-library/react'; 2import { BroadcastProvider } from 'react-broadcast-sync'; 3 4test('should render messages from provider', () => { 5 render( 6 <BroadcastProvider channelName="test-channel"> 7 <TestComponent /> 8 </BroadcastProvider> 9 ); 10 11 // Your test assertions here 12});
Relies on BroadcastChannel API:
We're actively improving react-broadcast-sync
! Here are some features and enhancements planned for upcoming versions:
Integration Tests
Ensure robust behavior and edge-case coverage.
Automatic Channel Recovery
Reconnect automatically if the BroadcastChannel
gets disconnected or closed by the browser.
clearMessagesByType()
Clear all messages of a specific type with a single call.
Per-Type Callbacks
Define message handlers for specific types with onMessage({ type, callback })
.
clearAllSentMessages()
/ clearAllReceivedMessages()
Fine-grained control for clearing messages based on source.
We're committed to keeping this package lightweight, flexible, and production-ready.
Your feedback and contributions are welcome — feel free to open an issue!
This project uses Semantic Release for fully automated versioning and changelog generation.
Every push to the main
branch with a Conventional Commit message triggers a release that includes:
major
, minor
, or patch
)1feat: add support for per-type callbacks 2fix: debounce cleanup runs properly on tab reload 3chore: update dependencies 4 5--- 6 7## Contributing 8 9PRs and feature suggestions welcome! Open an issue or submit a pull request. 10 11--- 12 13## License 14 15MIT © [Idan Shalem](https://github.com/IdanShalem)
No vulnerabilities found.
No security vulnerabilities found.