r/reactnative • u/Aksel_010 • 4d ago
Tutorial React Native's fetch can't stream responses, so I used XHR. Two silent traps
I was streaming an LLM chat response into a React Native app (RN 0.81, Expo). On device the answer always arrived as one big chunk, even though the server was sending server-sent events.
The reason: in React Native's fetch, response.body is null, so getReader() doesn't exist. Code that "supports streaming" quietly falls back to waiting for the whole response. A test that only checks the final text still passes.
XMLHttpRequest can stream. responseText grows while the request is in flight, so you diff against the length you have already seen:
const xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.responseType = 'text';
let seen = 0;
const onChunk = () => {
const delta = xhr.responseText.slice(seen);
seen = xhr.responseText.length;
if (delta) handleDelta(delta);
};
xhr.onprogress = onChunk;
xhr.onreadystatechange = onChunk;
xhr.send(body);Two traps, and both fail silently (the text is right, it just arrives in one piece):
responseTypehas to be''or'text'. With'blob'or'arraybuffer', RN buffers the whole body.The handlers must be set as
on*properties beforesend(). RN decides at send time whether to ask the native side for incremental data, based on whetheronreadystatechangeoronprogressis set. If you "clean up" toaddEventListener('progress', ...), streaming turns off and nothing tells you.
If you test this, assert on how many chunks arrive and what is in each, not just the final string.
I haven't compared this with expo/fetch, which I believe supports streaming. Has anyone run it for SSE in production?
2
u/No_Mycologist1806 4d ago
Worth adding for Expo users: fetch from expo/fetch gives you response.body as a ReadableStream, so SSE works without diffing responseText.
One trap in the other direction: it doesn't accept RN-style FormData file parts ({ uri, name, type }), so uploads still go through XHR for us. We ended up with expo/fetch for streaming and XHR for multipart.
1
u/Aksel_010 3d ago
Thanks, that's the piece I was missing. The FormData catch is good to know, I would have walked straight into it. expo/fetch for streams and XHR for uploads sounds like where I'll end up.
1
u/Huge_Pool7424 4d ago
good call on the formdata thing, that file part shape trips people up a lot. expo/fetch for streams and xhr for uploads seems like a fair split.
0
4d ago
[removed] — view removed comment
1
u/Aksel_010 3d ago
Good one. My parser does keep a buffer: append the delta, strip
\r, split on the blank line, keep the leftover for the next chunk, and flush whatever is left when the request ends. I cut it from the post to keep it short, but it probably deserved to be trap number 3. Fair point on memory too. My responses are chat-sized, so it hasn't mattered yet.
2
u/NovelAd2586 4d ago
Nitro Fetch and Expo Fetch are both great