-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React is jumbling up a websocket stream of data #247
Comments
Can you give me an example of your top level application specifically how/where you are using
It looks to me like you are asking the bot the same question twice? Though it's also strange that in both of your examples, the "actual" rendered answer has typos/incorrect grammar... There's also a few things that stand out to me in your code:
While you created a shallow clone of your
My suggestions to debug this would be to start from the basics -- instead of going straight for a I'd also add a
This will allow you to see the order in which the messages are being passed in, without worrying about bugs that might be introduced when you add complexity like Also, instead of your
That callback is invoked directly from the websocket message callback, and so it's closer to the source than |
I had the same thing happen to me as well. Yes, I understand hooks like these are intended to hold server-side state and serve as global caches of data, to permit calling them from anywhere. In my case, I am combining data from a I believe where I went wrong is that I ended up storing the raw copy of the data ( Does that sound reasonable? Open to any other ideas of course. The data corruption issue is transient so it is not totally obvious whether my change actually worked or not. |
Update here - my change did not actually work. I can confirm that (1) my back-end websocket server produces valid JSON, (2) |
@gandhis1 There isn't a whole lot going on with
const [lastMessage, setLastMessage] = useState<WebSocketEventMap['message'] | null>(null); Which is called directly in webSocketInstance.onmessage = (message: WebSocketEventMap['message']) => {
optionsRef.current.onMessage && optionsRef.current.onMessage(message);
if (typeof lastMessageTime?.current === 'number') {
lastMessageTime.current = Date.now();
}
if (typeof optionsRef.current.filter === 'function' && optionsRef.current.filter(message) !== true) {
return;
}
if (
optionsRef.current.heartbeat &&
typeof optionsRef.current.heartbeat !== "boolean" &&
optionsRef.current.heartbeat?.returnMessage === message.data
) {
return;
}
setLastMessage(message);
};
}; And finally passed through const lastJsonMessage: T = useMemo(() => {
if (lastMessage) {
try {
return JSON.parse(lastMessage.data);
} catch (e) {
return UNPARSABLE_JSON_OBJECT;
}
}
return null;
}, [lastMessage]); |
@gandhis1 That said, if you are using a shared websocket, then all components that call the same endpoint are consuming the same const updateMessageHistory = (
message: string,
role: "assistant" | "user",
isNewMessage: boolean
) => {
setMessageHistory((prev) => {
const newHistory = [...prev];
if (!isNewMessage && newHistory.length > 0) {
const lastMessage = newHistory[newHistory.length - 1];
if (lastMessage.role === role) {
lastMessage.message = message;
return newHistory;
}
}
newHistory.push({
id: uid(),
message: message,
role: role,
date: new Date(),
});
return newHistory;
});
}; In particular this: const lastMessage = newHistory[newHistory.length - 1];
if (lastMessage.role === role) {
lastMessage.message = message;
return newHistory;
} It seems like a pretty reasonable solution to update a chat message if the same user posts twice in a row, but this pattern is dangerous if multiple components are calling // Component responsible for displaying chat history
const { lastMessage } = useWebSocket('wss://chat-endpoint.com', { share: true });
const [chatHistory, setChatHistory] = useState([]);
useEffect(() => {
if (lastMessage) {
lastMessage.data = JSON.parse(lastMessage.data);
setChatHistory(lastMessage);
}
}, [lastMessage]); // Component responsible for displaying a toast notification whenever a message is received
const { lastMessage } = useWebSocket('wss://chat-endpoint.com', { share: true });
const jsonMessage = useMemo(() => {
return JSON.parse(lastMessage.data);
}, [lastMessage]);
useEffect(() => {
messageToast({ message: jsonMessage.message, dateReceived: jsonMessage.createdAt })
}, [jsonMessage]); If the second component renders after the first, then it is going to throw an error when it calls |
So a few additional points:
Here is an example of a corrupted message: In a follow-up, I will paste some anonymized/simplified code here that precisely represents my usage pattern. |
Here is what I am doing. This has been paraphrased / anonymized. App.tsx (application root) function GlobalFeeds (
// This is a component so this only renders itself and nothing else
// The websocket interacts with the rest of the application through the global store
useGlobalItems()
return null;
)
function MyRootComponent (
return <>
<MyComponent>
<GlobalFeeds>
</>
) useGlobalItems hook import axios from "axios";
import { format, isToday } from "date-fns";
import { useEffect, useMemo } from "react";
import { useQuery } from "react-query";
import useWebSocket from "react-use-websocket";
import useGlobalStore from "../stores/globalStore";
import Item from "../types/Item";
export default function useGlobalItems() {
const asOfDate = useGlobalStore((state) => state.asOfDate);
const liveMode = isToday(asOfDate);
// Live items
const webSocketHook = useWebSocket<Item[] | undefined>(
import.meta.env.VITE_WEBSOCKET_ITEMS,
{
shouldReconnect: () => true,
reconnectInterval: 1000,
reconnectAttempts: 60,
retryOnError: true,
},
liveMode,
);
// Historical items
const queryResult = useQuery(
["items", asOfDate],
async () =>
await axios
.get<Item[]>("/items", {
params: { asof_date: format(asOfDate, "yyyy-MM-dd") },
})
.then((res) => res.data),
{
enabled: !liveMode,
staleTime: 300_000, // 5 minutes
refetchOnMount: "always",
refetchOnWindowFocus: "always",
},
);
// Global item configuration toggles
const myToggle1 = useGlobalStore(
(state) => state.myToggle1,
);
// Post-process items
const liveItems = webSocketHook.lastJsonMessage;
const historicalItems = queryResult.data;
const rawItems = liveMode ? liveItems : historicalItems;
console.log("webSocketHook", webSocketHook); // **************** THIS lastMessage CORRUPTED AFTER HOURS OF WORKING *****************
console.log("liveMode", liveMode);
console.log("liveItems", liveItems);
console.log("historicalItems", historicalItems);
const {
items,
} = useMemo(() => {
if (!rawItems || !Array.isArray(rawItems)) {
return {
items: undefined,
};
}
return postProcessItems(
rawItems,
myToggle1
);
}, [
rawItems,
myToggle1
]);
// Synchronize to global store
useEffect(() => {
useGlobalStore.setState({items});
}, [
items,
]);
}
function postProcessItems(
items: Item[],
myToggle1: boolean
) {
const processedItems = [];
for (const item of items) {
// this makes a shallow copy
const processedItem: Item = {
...item,
// Override some stuff based on myToggle1
};
processedItems.push(processedItem);
}
return {
items: processedItems.length > 0 ? processedItems : undefined,
};
} In the above code |
One other point. So given the reality that the websocket connection could get into a bad state where it permanently corrupts incoming messages, I decided to explore (1) detecting when the incoming data is corrupt (2) resetting the websocket connection when it happens. I did this by adding extra cruft (a UUID) that gets ignored to the websocket URL, and then changing that in a Turns out that this approach works. Nothing in the websocket server changed, nothing in the client JS code change, I only just reconnected from scratch. Reconnecting fixes the corruption problems. So I'm still leaning towards it being something in the library itself that is going into a bad state. |
Apologies for the repeated posts, but just want to give full color. One thing I did not fully troubleshoot is whether this is a browser-level issue. If all this library is doing is using the browser's websocket API, then the problem could be in the browser. I will check the "Network" / "WS" tab the next time I am able to replicate to see whether the raw response is also corrupted. And if it is, I have no idea what that means. Could be an internal firewall/proxy issue even. |
Hello, I'm seeking help on why data is being jumbled, missed, and dropped when rendering from a websocket. I want to try making a ai chatbot for fun and learn about how websockets and SSE work. I have been banging my head for a few hours now because data is not appearing at it should.
Example
I have a fast api and is streaming data from openai api and sending it to a website. i have confirmed with backend logs and with the firefox websocket network tab that the data is getting to the website in order and all intact.
For example:
Q: what is your favorite color?
Expected A: "I don't have personal preferences, but I can help you find information about colors or suggest color schemes if you need assistance with that!"
Rendered A: "I have preferences but I help you find about or color schemes you need with"
Q: Tell me a joke
Expected A: "Sure, here's a joke for you:\n\nWhy couldn't the bicycle stand up by itself?\n\nBecause it was two tired!"
Rendered A: "Sure's for couldn stand by it was"
Code
I'm just streaming data token by token from the open api chat in a
content
payload. then i tried to fix it by adding asummary
payload which sends the entire message once it's all done. But that is not helping.The text was updated successfully, but these errors were encountered: