Trading ticks
Market data is the textbook case for receive-side backpressure: 50+ messages per second, every one identically shaped, with the only thing that matters being the latest value. Re-rendering on every tick is correct semantics and ruinous performance. Batching collapses the burst into one render per flush window with the same latest-value guarantee.
Two panels driven by the same wire stream. Left:
useSocketEvent, one render per tick. Right:
useSocketEventBatch at
flushMs: 100, ~10 renders/sec for the same
data.
Live demo
Trading ticks
idleThe market feed pushes 50 quotes per second. The left panel re-renders on every tick — fine for one badge, ruinous for a real ladder. The right panel buffers and flushes every 100ms; same data, ~6× fewer renders. Watch Last batch on the right to see batching happen — each flush surfaces the size of the buffer that just drained.
Press Start to begin.
Press Start to begin.
Per-event (the bad one)
function PerEventQuote({ manager }) {
const [latest, setLatest] = useState<TServerMsg | null>(null);
const [received, setReceived] = useState(0);
useSocketEvent(manager, "tick", (msg) => {
setLatest(msg);
setReceived((n) => n + 1);
});
return <Quote bid={latest?.bid} ask={latest?.ask} last={latest?.last} />;
}Batched (the right one)
function BatchedQuote({ manager }) {
const [latest, setLatest] = useState<TServerMsg | null>(null);
const [received, setReceived] = useState(0);
useSocketEventBatch(
manager,
"tick",
(msgs) => {
if (msgs.length === 0) return;
setReceived((n) => n + msgs.length);
// Only the last value matters; intermediate ticks would just thrash the DOM
setLatest(msgs[msgs.length - 1]);
},
{ flushMs: 100 },
);
return <Quote bid={latest?.bid} ask={latest?.ask} last={latest?.last} />;
}When you don't want batching
Batching is wrong when each event matters individually. Order
fills, trade confirmations, position updates — anything you would
log or alert on per event — must use
useSocketEvent. Use batching only for
"snapshot of the latest value" data.