From cab27499f1cca22f28b0298d99aa4b3ab8c380a5 Mon Sep 17 00:00:00 2001 From: Howard Gil Date: Tue, 24 Feb 2026 13:23:16 -0800 Subject: [PATCH] fix: deduplicate messages to prevent duplicate React keys during streaming During streaming, agent.messages can contain entries with duplicate IDs, causing React key warnings and UI jank. This deduplicates messages in CopilotChat (keeping the last occurrence) and adds index-based keys in CopilotChatMessageView as a defensive guard. Closes #3258 Co-Authored-By: Claude Opus 4.6 --- .../v2/react/src/components/chat/CopilotChat.tsx | 10 ++++++---- .../components/chat/CopilotChatMessageView.tsx | 15 ++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/v2/react/src/components/chat/CopilotChat.tsx b/packages/v2/react/src/components/chat/CopilotChat.tsx index 3c9a1b555f4..3cddae14416 100644 --- a/packages/v2/react/src/components/chat/CopilotChat.tsx +++ b/packages/v2/react/src/components/chat/CopilotChat.tsx @@ -281,10 +281,12 @@ export function CopilotChat({ // Memoize messages array - only create new reference when content actually changes // (agent.messages is mutated in place, so we need a new reference for React to detect changes) - const messages = useMemo( - () => [...agent.messages], - [JSON.stringify(agent.messages)], - ); + const messages = useMemo(() => { + const seen = new Map(); + const raw = [...agent.messages]; + raw.forEach((msg, i) => seen.set(msg.id, i)); + return raw.filter((msg, i) => seen.get(msg.id) === i); + }, [JSON.stringify(agent.messages)]); const finalProps = merge(mergedProps, { messages, diff --git a/packages/v2/react/src/components/chat/CopilotChatMessageView.tsx b/packages/v2/react/src/components/chat/CopilotChatMessageView.tsx index b0258b94c26..fe65ec35443 100644 --- a/packages/v2/react/src/components/chat/CopilotChatMessageView.tsx +++ b/packages/v2/react/src/components/chat/CopilotChatMessageView.tsx @@ -356,7 +356,8 @@ export function CopilotChatMessageView({ }; const messageElements: React.ReactElement[] = messages - .flatMap((message) => { + .flatMap((message, idx) => { + const keyBase = `${message.id}-${idx}`; const elements: (React.ReactElement | null | undefined)[] = []; const stateSnapshot = getStateSnapshotForMessage(message.id); @@ -364,7 +365,7 @@ export function CopilotChatMessageView({ if (renderCustomMessage) { elements.push( , @@ -463,7 +464,7 @@ export function CopilotChatMessageView({ elements.push(