Preserve memoization of message list (#10434)

This commit is contained in:
DS Boyce
2026-05-08 10:54:08 -07:00
committed by GitHub
parent 92fdb6630d
commit 5640aed7e3
6 changed files with 72 additions and 113 deletions
-5
View File
@@ -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
+18 -11
View File
@@ -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<string, ChatBskyActorDefs.ProfileViewBasic>
}): 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 = (
<LayoutAnimationConfig skipEntering skipExiting>
@@ -368,7 +375,7 @@ let MessageItem = ({
) : null}
<ReactionsDialog
control={reactionsControl}
relatedProfiles={item.relatedProfiles}
relatedProfiles={relatedProfiles}
message={message}
reactions={message.reactions}
groupedReactions={groupedReactions}
+4 -1
View File
@@ -1,4 +1,5 @@
import {View} from 'react-native'
import {type ChatBskyActorDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro'
import {type ConvoItem} from '#/state/messages/convo/types'
@@ -8,13 +9,15 @@ import {Text} from '#/components/Typography'
export function SystemMessageItem({
item,
relatedProfiles,
}: {
item: ConvoItem & {type: 'system-message'}
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
}) {
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
@@ -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 (
<MessageItem
item={item}
isGroupChat={convoState.convo.kind === 'group'}
prevMessage={getNeighborMessage(convoState.items, index - 1)}
nextMessage={getNeighborMessage(convoState.items, index + 1)}
relatedProfiles={convoState.relatedProfiles}
/>
)
} else if (item.type === 'deleted-message') {
return <Text>Deleted message</Text>
} else if (item.type === 'system-message') {
return <SystemMessageItem item={item} />
return (
<SystemMessageItem
item={item}
relatedProfiles={convoState.relatedProfiles}
/>
)
} else if (item.type === 'error') {
return <MessageListError item={item} />
}
+14 -66
View File
@@ -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
})
}
/**
+4 -28
View File
@@ -72,29 +72,11 @@ export type ConvoItem =
type: 'message'
key: string
message: ChatBskyConvoDefs.MessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
}
| {
type: 'pending-message'
key: string
message: ChatBskyConvoDefs.MessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
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<string, ChatBskyActorDefs.ProfileViewBasic>
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
}
| {
type: 'system-message'
key: string
message: ChatBskyConvoDefs.SystemMessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
}
| {
type: 'error'
@@ -172,6 +144,7 @@ export type ConvoStateReady = {
status: ConvoStatus.Ready
items: ConvoItem[]
convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined
isFetchingHistory: boolean
hasAllHistory: boolean
@@ -186,6 +159,7 @@ export type ConvoStateBackgrounded = {
status: ConvoStatus.Backgrounded
items: ConvoItem[]
convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined
isFetchingHistory: boolean
hasAllHistory: boolean
@@ -200,6 +174,7 @@ export type ConvoStateSuspended = {
status: ConvoStatus.Suspended
items: ConvoItem[]
convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined
isFetchingHistory: boolean
hasAllHistory: boolean
@@ -228,6 +203,7 @@ export type ConvoStateDisabled = {
status: ConvoStatus.Disabled
items: ConvoItem[]
convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined
isFetchingHistory: boolean
hasAllHistory: boolean