useSend
useSend gives you a channel's post function with stable identity — the
"announce" half of the event system. It exists so you can hand a typed,
never-changing send function to child components, callbacks, and effects
without memoizing anything.
import { useChannel, useSend } from 'use-everywhere';
type ShopEvents = { 'cart-updated': { items: number } };
function AddToCart({ items }: { items: number }) {
const channel = useChannel<ShopEvents>('shop');
const send = useSend(channel);
return <button onClick={() => send('cart-updated', { items: items + 1 })}>Add to cart</button>;
}
Signature
function useSend<M extends MessageMap>(channel: Channel<M>): Channel<M>['post'];
// i.e. <K extends keyof M & string>(type: K, payload: M[K]) => void
No options. The returned function is the channel's own post, typed against
the channel's message map: event names autocomplete, payload shapes are
checked.
Return value
A (type, payload) => void function that broadcasts to every other tab,
window, and worker on the origin, fire-and-forget. Because the channel is a
page-wide singleton, the function's identity is stable across renders — safe
in dependency arrays, safe to pass down, no useCallback required.
Worked example: update yourself, then tell everyone
The one pattern to internalize — posts don't echo, so the sending tab updates itself explicitly first:
function CartBadge() {
const [items, setItems] = useState(0);
const channel = useChannel<ShopEvents>('shop');
const send = useSend(channel);
useMessage(channel, 'cart-updated', (p) => setItems(p.items)); // other tabs
const addToCart = () => {
setItems(items + 1); // 1. this tab, explicitly
send('cart-updated', { items: items + 1 }); // 2. every other tab
};
return <button onClick={addToCart}>Cart ({items})</button>;
}
If both sides of that pattern feel like ceremony for your use case, that's
usually a sign the value should be shared state
instead — one setCount and every tab (including yours) updates.
Gotchas
- Fire-and-forget. No ack, no retry, no queue. A tab that isn't listening right now misses the event, permanently.
- Never echoed to the sender. By design, same as raw BroadcastChannel — see the worked example above for the idiom.
- It's just sugar.
useSend(channel)returnschannel.post; callingchannel.post(...)directly is equally fine. Use whichever reads better.
Where to next
useMessage— the listening half, and the meta argument.useChannel— where the typing comes from.defineChannel— bind name and types once and skip the trio's ceremony.- Messages & presence guide — the trio in context.