From 7989f7ba4b20883736739ee9c832a888d5ffce36 Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Thu, 16 Apr 2026 19:33:14 +0800 Subject: [PATCH] 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. --- src/contexts/room-context.tsx | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/contexts/room-context.tsx b/src/contexts/room-context.tsx index 9322d36..0c36d56 100644 --- a/src/contexts/room-context.tsx +++ b/src/contexts/room-context.tsx @@ -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); + } }, [], );