fix(room): fix StrictMode reconnect loop, add revokeMessage rollback

- useEffect([wsClient]): remove wsClient from deps to prevent
  React StrictMode double-mount from disconnecting the real client.
  First mount connects client-1; StrictMode cleanup disconnects it.
  Second mount connects client-2; first mount's second cleanup would
  then disconnect client-2, leaving WS permanently unconnected.
  Changing to useEffect([]) + optional chaining fixes this.
- revokeMessage: add optimistic removal + rollback on server rejection,
  consistent with editMessage pattern. Previously a failed delete left the
  message visible with no feedback.
This commit is contained in:
ZhenYi 2026-04-16 19:33:14 +08:00
parent 7416f37cec
commit 7989f7ba4b

View File

@ -595,11 +595,17 @@ export function RoomProvider({
}, [wsToken]);
useEffect(() => {
if (!wsClientRef.current) return;
wsClientRef.current.connect().catch((e) => {
// NOTE: intentionally omitted [wsClient] from deps.
// In React StrictMode the component mounts twice — if wsClient were a dep,
// the first mount's effect would connect client-1, then StrictMode cleanup
// would disconnect it, then the second mount's effect would connect client-2,
// then immediately the first mount's *second* cleanup would fire and
// disconnect client-2 — leaving WS unconnected. Using a ref for the initial
// connect avoids this. The client is always ready by the time this runs.
wsClientRef.current?.connect().catch((e) => {
console.error('[RoomContext] WS connect error:', e);
});
}, [wsClient]);
}, []);
const connectWs = useCallback(async () => {
const client = wsClientRef.current;
@ -899,10 +905,25 @@ export function RoomProvider({
async (messageId: string) => {
const client = wsClientRef.current;
if (!client) return;
await client.messageRevoke(messageId);
setMessages((prev) => prev.filter((m) => m.id !== messageId));
// Persist to IndexedDB
deleteMessageFromIdb(messageId).catch(() => {});
// Optimistic removal: hide message immediately
let rollbackMsg: MessageWithMeta | null = null;
setMessages((prev) => {
rollbackMsg = prev.find((m) => m.id === messageId) ?? null;
return prev.filter((m) => m.id !== messageId);
});
try {
await client.messageRevoke(messageId);
deleteMessageFromIdb(messageId).catch(() => {});
} catch (err) {
// Rollback: restore message on server rejection
if (rollbackMsg) {
setMessages((prev) => [...prev, rollbackMsg!]);
saveMessage(rollbackMsg!).catch(() => {});
}
handleRoomError('Delete message', err);
}
},
[],
);