React Native + Stream Data: How to handle stream data like ChatGPT?
Nguyễn Hữu Hiếu
Posted on February 23, 2024
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
- Create API that simulates stream data
- Test API with your browser
- 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())
Step 2. Test API with your browser
- Open your browser
- Access link http://127.0.0.1:8000
- Open console and run code below
(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)
}
})()
Step 3. Create react-native app that receives stream data like ChatGPT
Install libs
npm i react-native-webview
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>
);
}
💖 💪 🙅 🚩
Nguyễn Hữu Hiếu
Posted on February 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.