DEV Community

Nguyễn Hữu Hiếu
Nguyễn Hữu Hiếu

Posted on • Updated on

React Native + Stream Data: How to handle stream data like ChatGPT?

Question: I want to create a chat UI that receives token by token like ChatGPT (stream data). But React Native does not support stream response. How to do it?

Ideal: we use react-native-webview to invoke API that can receive stream data
Thanks to the author of the react-native-chatgpt library

Step by step

  1. Create API that simulates stream data
  2. Test API with your browser
  3. Create react-native app that receives stream data like ChatGPT

Step 1. Create API that simulates stream data


"""
# filename: main.py
# To install
pip install fastapi
pip install "uvicorn[standard]"


# To run
uvicorn main:app --reload
"""
import asyncio
from fastapi import FastAPI
from starlette.responses import StreamingResponse
from time import time

app = FastAPI()


@app.get("/")
def read_root():
    return "Test stream"


async def generate_data():
    for i in range(5):
        yield f"message {i}\n"
        await asyncio.sleep(1)


@app.get("/stream")
def stream():
    return StreamingResponse(generate_data())
Enter fullscreen mode Exit fullscreen mode

Step 2. Test API with your browser

(async()=>{
    const response = await fetch('http://127.0.0.1:8000/stream', {
        method: 'GET',
        responseType: 'stream',
    });

    async function *streamAsyncIterable(stream) {
        const reader = stream.getReader()
        try {
            while (true) {
                const {done, value} = await reader.read()
                if (done) {
                    return
                }
                yield value
            }
        } finally {
            reader.releaseLock()
        }
    }

    for await(const chunk of streamAsyncIterable(response?.body)) {
        const str = new TextDecoder().decode(chunk);
        console.log(new Date().toISOString(), str)
    }

})()
Enter fullscreen mode Exit fullscreen mode

Result

Step 3. Create react-native app that receives stream data like ChatGPT

Install libs

npm i react-native-webview
Enter fullscreen mode Exit fullscreen mode

App.js

import React, { useRef, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";
import { WebView } from "react-native-webview";

export default function App() {
  const [message, setMessage] = useState("");
  const webviewRef = useRef();
  const getStreamData = () => {
    setMessage("");

    const injectScript = `
    (async()=>{
      const postMessage = window.ReactNativeWebView.postMessage;
      const response = await fetch('http://127.0.0.1:8000/stream', {
          method: 'GET',
          responseType: 'stream',
      });

      async function *streamAsyncIterable(stream) {
          const reader = stream.getReader()
          try {
              while (true) {
                  const {done, value} = await reader.read()
                  if (done) {
                      return
                  }
                  yield value
              }
          } finally {
              reader.releaseLock()
          }
      }

      for await(const chunk of streamAsyncIterable(response?.body)) {
          const str = new TextDecoder().decode(chunk);
          postMessage(new Date().toLocaleTimeString() + " " + str);
      }

    })()
    `;
    webviewRef?.current?.injectJavaScript(injectScript);
  };
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: "#d2d2d2",
        textAlign: "center",
        paddingVertical: 120,
      }}
    >
      <WebView
        ref={webviewRef}
        style={{
          height: 80,
          width: "100%",
        }}
        source={{ uri: "http://127.0.0.1:8000/" }}
        onMessage={(event) => {
          const data = event.nativeEvent.data;
          setMessage((prev) => prev + data);
        }}
      />

      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <Text>{message}</Text>
        <Button title="Press me" onPress={getStreamData}></Button>
      </View>
    </View>
  );
}

Enter fullscreen mode Exit fullscreen mode

Finally result

Top comments (1)

Collapse
 
ekimcem profile image
Ekim Cem Ülger

Is there anyway to do it without webview?