Broadcast to every call
Several upload pills stacked at once. One Upload.update(props) with no promise merges into every open call, so a single connection change flips them all — while each keeps its own filename.
Update Stacking
→ start some uploads, then broadcast to all of them
The Callable
declared once, mounted in the React treeimport { createCallable } from 'react-call'
type UploadState = 'uploading' | 'paused' | 'done'
interface Props { label: string state: UploadState}
const ROW_HEIGHT = 52 // px per stacked pill, including gap
const STATE_META: Record<UploadState, { icon: string; text: string }> = { uploading: { icon: '↑', text: 'uploading…' }, paused: { icon: '⏸', text: 'paused' }, done: { icon: '✓', text: 'done' },}
export const Upload = createCallable<Props, void>(({ call, label, state }) => { // call.index is this call's slot in the stack — offset each pill upward // so concurrent uploads stack instead of overlapping. const bottom = 24 + call.index * ROW_HEIGHT const { icon, text } = STATE_META[state]
return ( <div style={{ bottom }} className="pointer-events-none fixed right-6 z-50 transition-[bottom] duration-200" > <div className="pointer-events-auto flex w-72 items-center gap-3 rounded-md border border-[var(--color-border)] bg-[var(--color-bg)] px-4 py-2 text-sm text-[var(--color-fg)] shadow-2xl backdrop-blur"> <span aria-hidden="true">{icon}</span> <span className="flex-1 truncate">{label}</span> <span className="font-mono text-xs text-[var(--color-fg-subtle)]"> {text} </span> <button type="button" onClick={() => call.end()} aria-label="Dismiss" className="-mr-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-base leading-none text-[var(--color-fg-subtle)] transition-colors hover:bg-[var(--color-bg-subtle)] hover:text-[var(--color-fg)]" > × </button> </div> </div> )})Upload.displayName = 'Upload'The Root
mounted in your app tree, onceimport { Upload } from './Upload'import { UploadQueueButton } from './UploadQueueButton'
export default function App() { return ( <> <Upload /> <UploadQueueButton /> </> )}The caller
anywhere in your app, imperativeimport { useState } from 'react'import { Upload } from './Upload'
const FILES = ['report.pdf', 'photo.jpg', 'archive.zip']
export const UploadQueueButton = () => { const [online, setOnline] = useState(true) const [openCount, setOpenCount] = useState(0)
const start = () => { setOnline(true) for (const label of FILES) { // The promise resolves when this pill is dismissed (× → call.end()), // so we can track how many are still open and re-enable Start at zero. const promise = Upload.call({ label, state: 'uploading' }) setOpenCount((n) => n + 1) promise.then(() => setOpenCount((n) => n - 1)) } }
// One update() with no promise broadcasts to EVERY open call. It merges // `state` into each pill's props, so they all flip together while each // keeps its own `label`. const toggleConnection = () => { const next = !online setOnline(next) Upload.update({ state: next ? 'uploading' : 'paused' }) }
const completeAll = () => { setOnline(true) Upload.update({ state: 'done' }) }
const idle = openCount === 0
return ( <div className="flex flex-col items-center gap-3"> <div className="flex flex-wrap justify-center gap-3"> <button type="button" onClick={start} disabled={!idle} className="rounded-md bg-[var(--color-accent)] px-4 py-2 text-sm font-medium text-[var(--color-accent-fg)] transition-colors hover:bg-[var(--color-accent-hover)] disabled:opacity-50" > Start 3 uploads </button> <button type="button" onClick={toggleConnection} disabled={idle} className="rounded-md border border-[var(--color-border)] px-4 py-2 text-sm font-medium text-[var(--color-fg-muted)] transition-colors hover:text-[var(--color-fg)] disabled:opacity-50" > Toggle connection </button> <button type="button" onClick={completeAll} disabled={idle} className="rounded-md border border-[var(--color-border)] px-4 py-2 text-sm font-medium text-[var(--color-fg-muted)] transition-colors hover:text-[var(--color-fg)] disabled:opacity-50" > Complete all </button> </div> <span className="font-mono text-xs text-[var(--color-fg-subtle)]"> {idle ? '→ start some uploads, then broadcast to all of them' : `connection: ${online ? 'online' : 'offline'} — ${openCount} open`} </span> </div> )}Update without a promise hits everyone
update has two forms. Pass a promise to target one call; omit it and the
new props broadcast to every open call:
Upload.update({ state: 'paused' }) // every open pill, at onceIt’s a shallow merge, not a replace — state lands on each call while
its own label stays put. That’s why one “connection lost” event can
pause three uploads without the caller tracking a single promise.
Broadcast vs. targeted
This is the counterpart to Live status:
- Targeted —
update(promise, props)pushes to one specific call the caller is tracking. Use it when each call has its own lifecycle. - Broadcast —
update(props)pushes to all of them. Use it for ambient state every open call should reflect: connectivity, a global mode, a shared clock.
Gotchas
- Merging means a partial broadcast leaves untouched props alone — handy, but double-check you’re not relying on a prop a stale call still holds.