From bf07b590eb1b462e9c4fd45fe70913096439f2ff Mon Sep 17 00:00:00 2001 From: Onur Aycicek Date: Mon, 11 May 2026 00:22:58 +0300 Subject: [PATCH] Optimize conversation unread selectors --- .../pages/help/ConversationsBox.tsx | 13 ++-- src-gui/src/store/hooks.ts | 61 +++++++++++-------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx b/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx index def0072b01..5a51555243 100644 --- a/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx +++ b/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx @@ -46,14 +46,17 @@ import { Message, PrimitiveDateTimeString } from "models/apiModel"; import { formatDateTime } from "utils/conversionUtils"; import { Theme } from "renderer/components/theme"; +const EMPTY_MESSAGES: Message[] = []; + // Hook: sorted feedback IDs by latest activity, then unread function useSortedFeedbackIds() { const ids = useAppSelector((s) => s.conversations.knownFeedbackIds || []); const conv = useAppSelector((s) => s.conversations.conversations); - const seen = useAppSelector((s) => new Set(s.conversations.seenMessages)); + const seenMessages = useAppSelector((s) => s.conversations.seenMessages); return useMemo(() => { + const seen = new Set(seenMessages); const arr = ids.map((id) => { - const msgs = conv[id] || []; + const msgs = conv[id] ?? EMPTY_MESSAGES; const unread = msgs.filter( (m) => m.is_from_staff && !seen.has(m.id.toString()), ).length; @@ -74,7 +77,7 @@ function useSortedFeedbackIds() { b.latest - a.latest || (b.unread > 0 ? 1 : 0) - (a.unread > 0 ? 1 : 0), ); return arr.map((x) => x.id); - }, [ids, conv, seen]); + }, [ids, conv, seenMessages]); } // Main component @@ -173,7 +176,7 @@ function ConversationRow({ onOpen: (id: string) => void; }) { const msgs = useAppSelector( - (s) => s.conversations.conversations[feedbackId] || [], + (s) => s.conversations.conversations[feedbackId] ?? EMPTY_MESSAGES, ); const unread = useUnreadMessagesCount(feedbackId); const sorted = useMemo( @@ -245,7 +248,7 @@ function ConversationModal({ }) { const dispatch = useAppDispatch(); const msgs = useAppSelector( - (s) => s.conversations.conversations[feedbackId] || [], + (s) => s.conversations.conversations[feedbackId] ?? EMPTY_MESSAGES, ); const [newMessage, setNewMessage] = useState(""); const [sending, setSending] = useState(false); diff --git a/src-gui/src/store/hooks.ts b/src-gui/src/store/hooks.ts index 910bad3a2d..988a33a509 100644 --- a/src-gui/src/store/hooks.ts +++ b/src-gui/src/store/hooks.ts @@ -31,7 +31,7 @@ import { TauriBackgroundProgress, TauriBitcoinSyncProgress, } from "models/tauriModel"; -import { Alert } from "models/apiModel"; +import { Alert, Message } from "models/apiModel"; import { fnv1a } from "utils/hash"; import { selectAllSwapInfos, @@ -43,6 +43,8 @@ import { export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; +const EMPTY_MESSAGES: Message[] = []; + export function useResumeableSwapsCount( additionalFilter?: (s: GetSwapInfoResponseExt) => boolean, ) { @@ -300,19 +302,20 @@ export function useConservativeBitcoinSyncProgress(): TauriBitcoinSyncProgress | * @returns The number of unread staff messages. */ export function useUnreadMessagesCount(feedbackId: string): number { - const { conversationsMap, seenMessagesSet } = useAppSelector((state) => ({ - conversationsMap: state.conversations.conversations, - // Convert seenMessages array to a Set for efficient lookup - seenMessagesSet: new Set(state.conversations.seenMessages), - })); - - const messages = conversationsMap[feedbackId] || []; - - const unreadStaffMessages = messages.filter( - (msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()), + const messages = useAppSelector( + (state) => state.conversations.conversations[feedbackId] ?? EMPTY_MESSAGES, + ); + const seenMessages = useAppSelector( + (state) => state.conversations.seenMessages, ); - return unreadStaffMessages.length; + return useMemo(() => { + const seenMessagesSet = new Set(seenMessages); + + return messages.filter( + (msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()), + ).length; + }, [messages, seenMessages]); } /** @@ -320,21 +323,27 @@ export function useUnreadMessagesCount(feedbackId: string): number { * @returns The total number of unread staff messages. */ export function useTotalUnreadMessagesCount(): number { - const { conversationsMap, seenMessagesSet } = useAppSelector((state) => ({ - conversationsMap: state.conversations.conversations, - seenMessagesSet: new Set(state.conversations.seenMessages), - })); - - let totalUnreadCount = 0; - for (const feedbackId in conversationsMap) { - const messages = conversationsMap[feedbackId] || []; - const unreadStaffMessages = messages.filter( - (msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()), - ); - totalUnreadCount += unreadStaffMessages.length; - } + const conversationsMap = useAppSelector( + (state) => state.conversations.conversations, + ); + const seenMessages = useAppSelector( + (state) => state.conversations.seenMessages, + ); + + return useMemo(() => { + const seenMessagesSet = new Set(seenMessages); + + let totalUnreadCount = 0; + for (const feedbackId in conversationsMap) { + const messages = conversationsMap[feedbackId] ?? EMPTY_MESSAGES; + const unreadStaffMessages = messages.filter( + (msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()), + ); + totalUnreadCount += unreadStaffMessages.length; + } - return totalUnreadCount; + return totalUnreadCount; + }, [conversationsMap, seenMessages]); } /// Returns all the alerts that have not been acknowledged