Live status update
A pinned status pill. The caller pushes new props into the open call as work advances — same instance, updated from the outside via the promise reference.
Update
The Callable
declared once, mounted in the React treeimport { createCallable } from 'react-call'
type Stage = 'placed' | 'packing' | 'shipped' | 'out' | 'delivered'
const STAGES: Stage[] = ['placed', 'packing', 'shipped', 'out', 'delivered']
const LABELS: Record<Stage, string> = { placed: 'Order placed', packing: 'Packing your order', shipped: 'Handed to carrier', out: 'Out for delivery', delivered: 'Delivered',}
interface Props { stage: Stage}
export const Status = createCallable<Props, void>(({ call, stage }) => { const stepIndex = STAGES.indexOf(stage) const done = stage === 'delivered'
return ( <div className="pointer-events-none fixed right-6 bottom-6 z-50"> <div className="pointer-events-auto w-[280px] rounded-lg border border-[var(--color-border)] bg-[var(--color-bg)] p-4 shadow-2xl"> <div className="flex items-start justify-between gap-3"> <div> <p className="font-mono text-[10px] uppercase tracking-wider text-[var(--color-fg-subtle)]"> Order #4821 </p> <p className="mt-0.5 text-sm font-medium text-[var(--color-fg)]"> {LABELS[stage]} </p> </div> <button type="button" onClick={() => call.end()} aria-label={done ? 'Dismiss' : 'Stop watching'} className="-mr-1 inline-flex h-7 w-7 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 className="mt-3 flex gap-1"> {STAGES.map((s, i) => ( <div key={s} className={ i <= stepIndex ? 'h-1 flex-1 rounded-full bg-[var(--color-accent)] transition-colors' : 'h-1 flex-1 rounded-full bg-[var(--color-bg-muted)] transition-colors' } /> ))} </div> </div> </div> )})Status.displayName = 'Status'The Root
mounted in your app tree, onceimport { Status } from './Status'import { PlaceOrderButton } from './PlaceOrderButton'
export default function App() { return ( <> <Status /> <PlaceOrderButton /> </> )}The caller
anywhere in your app, imperativeimport { useState } from 'react'import { Status } from './Status'
type Stage = 'placed' | 'packing' | 'shipped' | 'out' | 'delivered'
const SEQUENCE: Stage[] = ['packing', 'shipped', 'out', 'delivered']
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
export const PlaceOrderButton = () => { const [running, setRunning] = useState(false)
const handleClick = async () => { setRunning(true) // The promise IS the call identity — hold the reference so we can // push updates to *this* specific open call later on. const promise = Status.call({ stage: 'placed' })
// Stop pushing if the user dismisses early. let dismissed = false promise.then(() => { dismissed = true })
for (const stage of SEQUENCE) { await sleep(900) if (dismissed) break // Re-render the open Status with new props — no new instance, // no upsert ambiguity, just this call. Status.update(promise, { stage }) }
await promise setRunning(false) }
return ( <button type="button" disabled={running} onClick={handleClick} 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" > {running ? 'Watching order…' : 'Place order'} </button> )}Why update and not upsert
call() returns a promise that doubles as the call identity. Pass
that same promise back to Callable.update(promise, newProps) and the
same open call re-renders with the new props.
That’s the differentiator from upsert: with
update, you choose exactly which open call to push to — and updates
only land if it’s still open. (Without the promise, the only fallback is
Callable.update(props), which broadcasts to every open instance.)
Multiple parallel orders, each tracked by its own promise, no fighting
for a singleton slot.
When to reach for it
- Long-running operations that emit progress from outside the Callable (websocket frames, server-sent events, timers).
- A live “viewers count” or “presence” indicator on an already-open dialog.
- Anywhere the call’s props need to change while it stays mounted, and the caller knows which specific call to target.
Gotchas
- Updates to a closed call are no-ops. If the user dismisses early,
subsequent
update(promise, …)calls are silently dropped. Track dismissal viapromise.then(…)if your loop needs to stop pushing.