Decoupling State from Render in LLM Streaming
The naive way to build a streaming AI interface is to pipe a Server-Sent Events stream straight into a React state setter: a chunk arrives, setState(prev => prev + chunk) fires, the component re-renders. At sub-50ms token intervals that is twenty or more reconciliation passes a second, each one walking the tree to diff a string that grew by a few characters. The frame budget makes the arithmetic unforgiving. Sixty frames per second leaves 16ms per frame, and the browser claims roughly 6ms of that for its own rendering work, so application code has about 10ms to do everything else. A pass that overruns the budget does not render late. The frame is dropped.
The fix is to stop treating arrival and display as the same event. I buffer incoming chunks in a mutable useRef, which accepts writes synchronously without scheduling anything, then flush to state on a requestAnimationFrame tick, so the DOM is written at most once per frame and only when the browser is about to paint. Throughput and render frequency come apart: the stream arrives as fast as the model emits, and the interface still paints on a steady cadence. This separation is older than React and older than LLMs, since a game loop makes the same split between simulation and draw. When the producer is faster than the consumer, the answer is a buffer and a clock, not a faster consumer.