Skip to Content
TutorialsNext.jsStreaming Subscriptions

Streaming Subscriptions

The useAppChat hook provides a pub/sub system for streaming text updates that bypasses React’s state batching. This is important for chat UIs where you want to render tokens as they arrive without waiting for React to batch and flush state updates.

The Problem

During streaming, the SDK fires onData callbacks rapidly (often 10–50 times per second). Updating React state on each callback triggers re-renders that can’t keep up, causing visible lag and dropped frames. The streaming text appears to update in chunks rather than smoothly.

The Solution

Instead of updating React state on each token, useAppChat accumulates the streamed text in a ref and notifies subscribers through a callback set. Each subscriber can update the DOM directly (e.g. setting textContent on a ref) without going through React’s reconciliation:

const subscribeToStreaming = useCallback( (callback: (text: string) => void) => { streamingCallbacksRef.current.add(callback); return () => { streamingCallbacksRef.current.delete(callback); }; }, [] ); const subscribeToThinking = useCallback( (callback: (text: string) => void) => { thinkingCallbacksRef.current.add(callback); return () => { thinkingCallbacksRef.current.delete(callback); }; }, [] );

hooks/useAppChat.ts 

Usage

Subscribe from your chat message component. The callback receives the full accumulated text (not individual chunks), so you can replace the element’s content directly:

const contentRef = useRef<HTMLDivElement>(null); useEffect(() => { const unsubscribe = subscribeToStreaming((text) => { if (contentRef.current) { contentRef.current.textContent = text; } }); return unsubscribe; }, [subscribeToStreaming]);

For markdown rendering, you’d parse and render the accumulated text on each callback. The key advantage is that the callback fires synchronously — there’s no React render cycle between the token arriving and the DOM updating.

Thinking Subscriptions

The same pattern applies to thinking/reasoning tokens via subscribeToThinking. See Thinking Mode for details.

When to Use React State Instead

After streaming completes, the final text is synced to React state in the post-stream cleanup phase (see Sending Messages). The streaming subscription is only needed during active streaming — once the response is complete, the message is available through the normal messages array.

Last updated on