How do I deal with a streamed response ?
using the function approach of setState
with functions doesn’t seem to work:
the hooks are not executed in the correct order and the response is unordered
const response = await fetch('/dostuff', {
method:'POST',
body
})
const reader = response.body!.getReader();
const chunks = [];
let done, value;
while (!done) {
({ value, done } = await reader.read());
if (done) {
return chunks;
}
const strval = new TextDecoder().decode(value)
chunks.push(strval);
await setResponseText((prev)=> {
return prev + strval
})
// console.log("EEE ",strval);
}
2
Answers
In the end the issue was in two different points:
Decoder
the decoder needed to be outside of the loop because the stream response could end in the middle of a multibyte character. creating a new decoder at each iteration does not allow to manage this case.
Strict mode
I honestly did not think that the useEffect hook was somehow involved in this error so I limited the code in the original question, my bad.
What happens is that in development mode, react strict mode causes the render and mounting / unmounting of components twice.
This creates a racing condition because the first render's streamed response was still active and receiving data when the second render's streamed response started.
The state that contains the response text is therefore updated sometimes with a first stream data chunk, sometime with a second stream chunk.
The solution was therefore to add an AbortController in the useEffect call.
The useEffect hook is used to initiate the fetching process, and the responseText state is updated once at the end of the loop. This should help avoid potential issues with asynchronous state updates. You can add any dependencies that should trigger the effect to run again when they change.