From 5640aed7e324a4c4b75987d37fd2d534fba130e7 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Fri, 8 May 2026 10:54:08 -0700 Subject: [PATCH] Preserve memoization of message list (#10434) --- eslint-suppressions.json | 5 -- src/components/dms/MessageItem.tsx | 29 ++++--- src/components/dms/SystemMessageItem.tsx | 5 +- .../Messages/components/MessagesList.tsx | 34 +++++++- src/state/messages/convo/agent.ts | 80 ++++--------------- src/state/messages/convo/types.ts | 32 +------- 6 files changed, 72 insertions(+), 113 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 87306cec64..ca3c9e9e0a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -479,11 +479,6 @@ "count": 3 } }, - "src/state/messages/convo/agent.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, "src/state/messages/events/agent.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index 6cbdf6a781..0f39e78ac2 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -20,6 +20,7 @@ import Animated, { } from 'react-native-reanimated' import { AppBskyEmbedRecord, + type ChatBskyActorDefs, ChatBskyConvoDefs, RichText as RichTextAPI, } from '@atproto/api' @@ -87,9 +88,21 @@ function isWithinClusterBoundary({ let MessageItem = ({ item, isGroupChat = false, + prevMessage, + nextMessage, + relatedProfiles, }: { item: ConvoItem & {type: 'message' | 'pending-message'} isGroupChat?: boolean + prevMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null + nextMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null + relatedProfiles: Map }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() @@ -97,11 +110,11 @@ let MessageItem = ({ const moderationOpts = useModerationOpts() const queryClient = useQueryClient() - const profile = item.relatedProfiles.get(item.message.sender.did) + const {message} = item + const profile = relatedProfiles.get(message.sender.did) const reactionsControl = useDialogControl() - const {message, nextMessage, prevMessage} = item const isPending = item.type === 'pending-message' const displayName = profile ? createSanitizedDisplayName(profile) : null @@ -268,7 +281,7 @@ let MessageItem = ({ return l`You reacted ${reaction.value}` } else { const senderDid = reaction.sender.did - const memberSender = item.relatedProfiles.get(senderDid) + const memberSender = relatedProfiles.get(senderDid) if (memberSender) { return l`${createSanitizedDisplayName(memberSender)} reacted ${reaction.value}` } @@ -279,13 +292,7 @@ let MessageItem = ({ one: '# person', other: '# people', })} reacted – ${groupedReactions.map(g => g.value).join(' ')}` - }, [ - reactions, - groupedReactions, - currentAccount?.did, - item.relatedProfiles, - l, - ]) + }, [reactions, groupedReactions, currentAccount?.did, relatedProfiles, l]) const appliedReactions = ( @@ -368,7 +375,7 @@ let MessageItem = ({ ) : null} }) { const t = useTheme() const {i18n} = useLingui() - const info = getSystemMessageInfo(item.message.data, item.relatedProfiles) + const info = getSystemMessageInfo(item.message.data, relatedProfiles) if (!info) return null const {Icon, message} = info diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index e8745049d3..d42a32f71a 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -25,6 +25,7 @@ import { type $Typed, type AppBskyEmbedRecord, AppBskyRichtextFacet, + ChatBskyConvoDefs, RichText, } from '@atproto/api' import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect' @@ -85,6 +86,27 @@ function keyExtractor(item: ConvoItem) { return item.key } +function getNeighborMessage( + items: ConvoItem[], + index: number, +): ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView | null { + const neighbor = items[index] + if (!neighbor) return null + if ( + neighbor.type === 'message' || + neighbor.type === 'pending-message' || + neighbor.type === 'deleted-message' + ) { + if ( + ChatBskyConvoDefs.isMessageView(neighbor.message) || + ChatBskyConvoDefs.isDeletedMessageView(neighbor.message) + ) { + return neighbor.message + } + } + return null +} + function onScrollToIndexFailed() { // Placeholder function. You have to give FlatList something or else it will error. } @@ -368,18 +390,26 @@ export function MessagesList({ }) }, [flatListRef]) - const renderItem = ({item}: {item: ConvoItem}) => { + const renderItem = ({item, index}: {item: ConvoItem; index: number}) => { if (item.type === 'message' || item.type === 'pending-message') { return ( ) } else if (item.type === 'deleted-message') { return Deleted message } else if (item.type === 'system-message') { - return + return ( + + ) } else if (item.type === 'error') { return } diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 8552a490e2..6220d5faac 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -216,6 +216,7 @@ export class Convo { status: this.status, items: this.getItems(), convo: this.convo!, + relatedProfiles: this.relatedProfiles, error: undefined, ...shared, ...methods, @@ -226,6 +227,7 @@ export class Convo { status: this.status, items: this.getItems(), convo: this.convo!, + relatedProfiles: this.relatedProfiles, error: undefined, ...shared, ...methods, @@ -236,6 +238,7 @@ export class Convo { status: this.status, items: this.getItems(), convo: this.convo!, + relatedProfiles: this.relatedProfiles, error: undefined, ...shared, ...methods, @@ -246,6 +249,7 @@ export class Convo { status: this.status, items: this.getItems(), convo: this.convo!, + relatedProfiles: this.relatedProfiles, error: undefined, ...shared, ...methods, @@ -1078,7 +1082,8 @@ export class Convo { // continue queue processing await this.processPendingMessages() - } catch (e: any) { + } catch (err) { + const e = err as Error this.handleSendMessageFailure(e) this.isProcessingPendingMessages = false } @@ -1177,7 +1182,8 @@ export class Convo { this.commit() logger.debug(`sent ${this.pendingMessages.size} pending messages`, {}) - } catch (e: any) { + } catch (err) { + const e = err as Error this.handleSendMessageFailure(e) } } @@ -1231,25 +1237,18 @@ export class Convo { type: 'message', key: m.id, message: m, - relatedProfiles: this.relatedProfiles, - nextMessage: null, - prevMessage: null, }) } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { items.unshift({ type: 'deleted-message', key: m.id, message: m, - relatedProfiles: this.relatedProfiles, - nextMessage: null, - prevMessage: null, }) } else if (ChatBskyConvoDefs.isSystemMessageView(m)) { items.unshift({ type: 'system-message', key: m.id, message: m, - relatedProfiles: this.relatedProfiles, }) } }) @@ -1271,25 +1270,18 @@ export class Convo { type: 'message', key: m.id, message: m, - relatedProfiles: this.relatedProfiles, - nextMessage: null, - prevMessage: null, }) } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { items.push({ type: 'deleted-message', key: m.id, message: m, - relatedProfiles: this.relatedProfiles, - nextMessage: null, - prevMessage: null, }) } else if (ChatBskyConvoDefs.isSystemMessageView(m)) { items.push({ type: 'system-message', key: m.id, message: m, - relatedProfiles: this.relatedProfiles, }) } }) @@ -1310,9 +1302,6 @@ export class Convo { did: this.senderUserDid, }, }, - relatedProfiles: this.relatedProfiles, - nextMessage: null, - prevMessage: null, failed: this.pendingMessageFailure !== null, retry: this.pendingMessageFailure === 'recoverable' @@ -1334,53 +1323,12 @@ export class Convo { }) } - return items - .filter(item => { - if (isConvoItemMessage(item)) { - return !this.deletedMessages.has(item.message.id) - } - return true - }) - .map((item, i, arr) => { - let nextMessage = null - let prevMessage = null - const isMessage = isConvoItemMessage(item) - - if (isMessage) { - if ( - ChatBskyConvoDefs.isMessageView(item.message) || - ChatBskyConvoDefs.isDeletedMessageView(item.message) - ) { - const next = arr[i + 1] - - if ( - isConvoItemMessage(next) && - (ChatBskyConvoDefs.isMessageView(next.message) || - ChatBskyConvoDefs.isDeletedMessageView(next.message)) - ) { - nextMessage = next.message - } - - const prev = arr[i - 1] - - if ( - isConvoItemMessage(prev) && - (ChatBskyConvoDefs.isMessageView(prev.message) || - ChatBskyConvoDefs.isDeletedMessageView(prev.message)) - ) { - prevMessage = prev.message - } - } - - return { - ...item, - nextMessage, - prevMessage, - } - } - - return item - }) + return items.filter(item => { + if (isConvoItemMessage(item)) { + return !this.deletedMessages.has(item.message.id) + } + return true + }) } /** diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index ad98afc3da..363c8291b5 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -72,29 +72,11 @@ export type ConvoItem = type: 'message' key: string message: ChatBskyConvoDefs.MessageView - relatedProfiles: Map - nextMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null - prevMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null } | { type: 'pending-message' key: string message: ChatBskyConvoDefs.MessageView - relatedProfiles: Map - nextMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null - prevMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null failed: boolean /** * Retry sending the message. If present, the message is in a failed state. @@ -105,21 +87,11 @@ export type ConvoItem = type: 'deleted-message' key: string message: ChatBskyConvoDefs.DeletedMessageView - relatedProfiles: Map - nextMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null - prevMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null } | { type: 'system-message' key: string message: ChatBskyConvoDefs.SystemMessageView - relatedProfiles: Map } | { type: 'error' @@ -172,6 +144,7 @@ export type ConvoStateReady = { status: ConvoStatus.Ready items: ConvoItem[] convo: ConvoWithDetails + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean @@ -186,6 +159,7 @@ export type ConvoStateBackgrounded = { status: ConvoStatus.Backgrounded items: ConvoItem[] convo: ConvoWithDetails + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean @@ -200,6 +174,7 @@ export type ConvoStateSuspended = { status: ConvoStatus.Suspended items: ConvoItem[] convo: ConvoWithDetails + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean @@ -228,6 +203,7 @@ export type ConvoStateDisabled = { status: ConvoStatus.Disabled items: ConvoItem[] convo: ConvoWithDetails + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean