AI — OpenAI Development

Streaming Chat UI in React Native with OpenAI

Direct answer

The stack that works reliably is a backend that relays OpenAI's streamed tokens over Server-Sent Events, consumed in React Native with an SSE client library or an incremental XMLHttpRequest reader. Streaming cuts perceived latency from several seconds to sub-second first paint, but a naive one-render-per-token implementation will drop frames — buffer incoming deltas and flush them to state every few dozen milliseconds, and only re-render the last message bubble.

Nothing kills an AI chat feature faster than a spinner sitting on screen for eight seconds. Streaming is the fix, but React Native's networking stack makes it less plug-and-play than on the web. This is the full path I ship: backend relay, client transport, and the rendering tricks that keep it at 60fps.

Key facts, with sources

  • At DevDay 2025 OpenAI reported 800 million weekly ChatGPT users, 4 million developers building on its platform, and roughly 8 billion API tokens processed per minute. (CNBC)
  • ChatGPT reached 900 million weekly active users by late February 2026, up from 800 million at DevDay in October 2025. (TechCrunch)
  • By March 2026 OpenAI's APIs were processing more than 15 billion tokens per minute, roughly doubling from the rate reported at DevDay 2025. (Panto AI OpenAI Statistics)
  • OpenAI's published API pricing discounts cached input tokens by 90 percent on supported GPT models, which materially cuts costs for agents that resend long system prompts. (OpenAI API Pricing Docs)
  • OpenAI raised $122 billion in new funding in 2026 to accelerate the next phase of AI development, one of the largest private raises in history. (OpenAI)

Why streaming is non-negotiable for chat UX

A full completion for a substantial answer often takes several seconds end to end. Users experience that as breakage: they re-tap, background the app, or churn. Streaming changes the contract — the first token typically arrives in well under a second, and from that moment the user is reading, not waiting. Total generation time barely matters once text is visibly flowing.

Streaming also unlocks honest cancellation. When a user can see the answer going in the wrong direction, they can stop it, which saves your output-token budget and their patience. Every chat interface people consider good streams; shipping a non-streaming chat UI in a mobile app today reads as broken regardless of how smart the model behind it is.

Transport: SSE through your backend beats everything else

Do not connect the app to OpenAI directly — the stream should come from your own backend, which holds the key and injects prompts. Between backend and app, Server-Sent Events is the right default: it is one-directional, which matches the problem, survives proxies and load balancers better than WebSockets, and is trivial to reason about. WebSockets earn their complexity only when you need bidirectional traffic like live voice.

The React Native catch: the built-in fetch has historically lacked readable stream support, so web-style response.body iteration does not work. The two practical options are an SSE client library such as react-native-sse, or a raw XMLHttpRequest reading responseText incrementally on progress events. Both are proven in production; the library route is less code to own.

Backend: a FastAPI relay endpoint

The backend requests a streamed completion from OpenAI and re-emits each delta as an SSE event. Keep the event payload minimal — the text delta and a done marker — and let the client own message assembly.

FastAPI SSE relay for OpenAI streaming
import json, os
from fastapi import FastAPI, Depends
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI

MODEL = os.environ["OPENAI_MODEL"]  # set to the latest model id
app = FastAPI()
client = AsyncOpenAI()

@app.post("/v1/chat/stream")
async def chat_stream(body: ChatRequest, user=Depends(get_current_user)):
    async def event_gen():
        stream = await client.chat.completions.create(
            model=MODEL, messages=body.messages, stream=True
        )
        async for chunk in stream:
            if chunk.choices and chunk.choices[0].delta.content:
                yield f"data: {json.dumps({'d': chunk.choices[0].delta.content})}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(event_gen(), media_type="text/event-stream")

Client: consuming the stream in React Native

With react-native-sse you can open a POST-based EventSource against your endpoint, accumulate deltas into the in-progress message, and close the connection on completion or unmount. The critical details are lifecycle ones: always close the source in the effect cleanup, and treat a close-without-done as an interrupted stream the user can retry.

Streaming consumer with buffered state updates
import EventSource from 'react-native-sse';

function streamReply(messages: Msg[], token: string, onDelta: (text: string) => void, onDone: () => void) {
  const es = new EventSource(`${API_BASE}/v1/chat/stream`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages }),
  });

  let buffer = '';
  const flush = setInterval(() => {
    if (buffer) { onDelta(buffer); buffer = ''; }  // batch UI updates ~20x/sec
  }, 50);

  es.addEventListener('message', (event) => {
    if (event.data === '[DONE]') {
      clearInterval(flush); if (buffer) onDelta(buffer);
      es.close(); onDone();
      return;
    }
    buffer += JSON.parse(event.data!).d;
  });

  return () => { clearInterval(flush); es.close(); };  // call on unmount/cancel
}

Rendering performance: never re-render per token

Tokens can arrive faster than frames. If every delta triggers a setState that re-renders your whole message list, you will see dropped frames and janky scroll precisely during the moment users are staring at the screen. Three rules fix it. First, buffer deltas and flush on an interval — around 50 milliseconds reads as perfectly live while capping renders at twenty per second. Second, isolate the in-progress message in its own component with its own state, so the FlatList of completed messages never re-renders during generation. Third, memoize message rows and keep them referentially stable.

If you render markdown, parse incrementally or defer full parsing until the message completes; re-parsing a growing markdown string on every flush is a common hidden cost that profiles as mysterious JS-thread saturation.

Failure modes: cancellation, backgrounding, mid-stream drops

Streams fail in ways request-response code does not. Users background the app mid-generation and iOS suspends the connection — persist the partial text and offer a regenerate action rather than pretending the stream will resume. Users navigate away — close the EventSource in cleanup or you leak connections and pay for unread tokens. Networks drop mid-stream — a resumed SSE connection does not replay missed tokens from OpenAI, so retry means resending the conversation and replacing the partial message.

Represent all this in message state explicitly: streaming, complete, interrupted, failed. An interrupted message with visible partial text plus a retry affordance is honest UX; a message that silently ends mid-sentence and looks finished is the bug users screenshot.

When to hire senior help

Bring in senior help when you move from a working prototype to production traffic, because cost controls, evals, rate-limit handling, and fallback behavior determine whether the unit economics work. An experienced engineer usually pays for themselves by cutting token spend and preventing outages rather than by writing the first prompt. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships AI — OpenAI Development projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Hardcoding a single flagship model ID for every call instead of routing by task, paying GPT-5-tier prices for classification work a nano-tier model handles at a fraction of the cost
  • Putting volatile content like timestamps and user IDs at the top of prompts, which breaks prefix caching and forfeits the 90 percent cached-input discount
  • Building on deprecated surfaces like the legacy Completions or wound-down fine-tuning APIs instead of the current Responses API and agent tooling
  • Launching with no spend caps or per-user rate limits, so a retry loop or a single abusive user burns a month's API budget overnight

Frequently asked questions

How do I stream OpenAI responses in React Native?

Relay the stream through your own backend over Server-Sent Events, since React Native's fetch has historically lacked readable stream support. On the client, use an SSE library like react-native-sse or an XMLHttpRequest reading responseText incrementally. Buffer incoming deltas and flush to state on a short interval — roughly 50 milliseconds — so rendering stays smooth.

Should I use WebSockets or SSE for AI chat streaming?

SSE for standard text chat. The traffic is one-directional — server to client — which is exactly what SSE models, and it passes through proxies and load balancers with less friction than WebSockets. Choose WebSockets only when you genuinely need bidirectional streaming, such as live voice conversations or collaborative sessions with concurrent server-bound events.

Why does my React Native chat UI lag while the AI is typing?

Almost always because every token triggers a state update that re-renders the entire message list. Batch deltas and flush every 50 milliseconds or so, render the in-progress message in an isolated component so completed messages never re-render, and memoize list rows. If you render markdown, defer full parsing until the message finishes streaming.

How much does it cost to build a product on the OpenAI API?

Pricing is per token: budget models start around $0.10 per million input tokens while flagship models run several dollars per million, with cached input discounted 90 percent. Most MVPs spend tens to low hundreds of dollars per month on inference until they have real traffic, at which point caching, batching, and model routing become the main cost levers.

Should we fine-tune a model or use prompting and RAG?

For most products, prompt engineering plus retrieval solves accuracy problems faster and cheaper than fine-tuning, and OpenAI has been winding down parts of its fine-tuning API. Fine-tuning mainly pays off for narrow, high-volume tasks with stable formats where you can amortize the effort.

How do we avoid getting locked into OpenAI?

Keep model calls behind a thin internal abstraction and maintain an eval suite so you can benchmark alternative providers on your actual tasks. Many production teams already run more than one provider and route by task, which also gives them a failover path during outages.

Bottom line: Dhairya Senjaliya ships AI — OpenAI Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership