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 "count": 3
} }
}, },
"src/state/messages/convo/agent.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
}
},
"src/state/messages/events/agent.ts": { "src/state/messages/events/agent.ts": {
"@typescript-eslint/no-explicit-any": { "@typescript-eslint/no-explicit-any": {
"count": 2 "count": 2
+18 -11
View File
@@ -20,6 +20,7 @@ import Animated, {
} from 'react-native-reanimated' } from 'react-native-reanimated'
import { import {
AppBskyEmbedRecord, AppBskyEmbedRecord,
type ChatBskyActorDefs,
ChatBskyConvoDefs, ChatBskyConvoDefs,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
@@ -87,9 +88,21 @@ function isWithinClusterBoundary({
let MessageItem = ({ let MessageItem = ({
item, item,
isGroupChat = false, isGroupChat = false,
prevMessage,
nextMessage,
relatedProfiles,
}: { }: {
item: ConvoItem & {type: 'message' | 'pending-message'} item: ConvoItem & {type: 'message' | 'pending-message'}
isGroupChat?: boolean isGroupChat?: boolean
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
}): React.ReactNode => { }): React.ReactNode => {
const t = useTheme() const t = useTheme()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -97,11 +110,11 @@ let MessageItem = ({
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const queryClient = useQueryClient() 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 reactionsControl = useDialogControl()
const {message, nextMessage, prevMessage} = item
const isPending = item.type === 'pending-message' const isPending = item.type === 'pending-message'
const displayName = profile ? createSanitizedDisplayName(profile) : null const displayName = profile ? createSanitizedDisplayName(profile) : null
@@ -268,7 +281,7 @@ let MessageItem = ({
return l`You reacted ${reaction.value}` return l`You reacted ${reaction.value}`
} else { } else {
const senderDid = reaction.sender.did const senderDid = reaction.sender.did
const memberSender = item.relatedProfiles.get(senderDid) const memberSender = relatedProfiles.get(senderDid)
if (memberSender) { if (memberSender) {
return l`${createSanitizedDisplayName(memberSender)} reacted ${reaction.value}` return l`${createSanitizedDisplayName(memberSender)} reacted ${reaction.value}`
} }
@@ -279,13 +292,7 @@ let MessageItem = ({
one: '# person', one: '# person',
other: '# people', other: '# people',
})} reacted ${groupedReactions.map(g => g.value).join(' ')}` })} reacted ${groupedReactions.map(g => g.value).join(' ')}`
}, [ }, [reactions, groupedReactions, currentAccount?.did, relatedProfiles, l])
reactions,
groupedReactions,
currentAccount?.did,
item.relatedProfiles,
l,
])
const appliedReactions = ( const appliedReactions = (
<LayoutAnimationConfig skipEntering skipExiting> <LayoutAnimationConfig skipEntering skipExiting>
@@ -368,7 +375,7 @@ let MessageItem = ({
) : null} ) : null}
<ReactionsDialog <ReactionsDialog
control={reactionsControl} control={reactionsControl}
relatedProfiles={item.relatedProfiles} relatedProfiles={relatedProfiles}
message={message} message={message}
reactions={message.reactions} reactions={message.reactions}
groupedReactions={groupedReactions} groupedReactions={groupedReactions}
+4 -1
View File
@@ -1,4 +1,5 @@
import {View} from 'react-native' import {View} from 'react-native'
import {type ChatBskyActorDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {type ConvoItem} from '#/state/messages/convo/types' import {type ConvoItem} from '#/state/messages/convo/types'
@@ -8,13 +9,15 @@ import {Text} from '#/components/Typography'
export function SystemMessageItem({ export function SystemMessageItem({
item, item,
relatedProfiles,
}: { }: {
item: ConvoItem & {type: 'system-message'} item: ConvoItem & {type: 'system-message'}
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
}) { }) {
const t = useTheme() const t = useTheme()
const {i18n} = useLingui() const {i18n} = useLingui()
const info = getSystemMessageInfo(item.message.data, item.relatedProfiles) const info = getSystemMessageInfo(item.message.data, relatedProfiles)
if (!info) return null if (!info) return null
const {Icon, message} = info const {Icon, message} = info
@@ -25,6 +25,7 @@ import {
type $Typed, type $Typed,
type AppBskyEmbedRecord, type AppBskyEmbedRecord,
AppBskyRichtextFacet, AppBskyRichtextFacet,
ChatBskyConvoDefs,
RichText, RichText,
} from '@atproto/api' } from '@atproto/api'
import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect' import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect'
@@ -85,6 +86,27 @@ function keyExtractor(item: ConvoItem) {
return item.key 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() { function onScrollToIndexFailed() {
// Placeholder function. You have to give FlatList something or else it will error. // Placeholder function. You have to give FlatList something or else it will error.
} }
@@ -368,18 +390,26 @@ export function MessagesList({
}) })
}, [flatListRef]) }, [flatListRef])
const renderItem = ({item}: {item: ConvoItem}) => { const renderItem = ({item, index}: {item: ConvoItem; index: number}) => {
if (item.type === 'message' || item.type === 'pending-message') { if (item.type === 'message' || item.type === 'pending-message') {
return ( return (
<MessageItem <MessageItem
item={item} item={item}
isGroupChat={convoState.convo.kind === 'group'} 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') { } else if (item.type === 'deleted-message') {
return <Text>Deleted message</Text> return <Text>Deleted message</Text>
} else if (item.type === 'system-message') { } else if (item.type === 'system-message') {
return <SystemMessageItem item={item} /> return (
<SystemMessageItem
item={item}
relatedProfiles={convoState.relatedProfiles}
/>
)
} else if (item.type === 'error') { } else if (item.type === 'error') {
return <MessageListError item={item} /> return <MessageListError item={item} />
} }
+14 -66
View File
@@ -216,6 +216,7 @@ export class Convo {
status: this.status, status: this.status,
items: this.getItems(), items: this.getItems(),
convo: this.convo!, convo: this.convo!,
relatedProfiles: this.relatedProfiles,
error: undefined, error: undefined,
...shared, ...shared,
...methods, ...methods,
@@ -226,6 +227,7 @@ export class Convo {
status: this.status, status: this.status,
items: this.getItems(), items: this.getItems(),
convo: this.convo!, convo: this.convo!,
relatedProfiles: this.relatedProfiles,
error: undefined, error: undefined,
...shared, ...shared,
...methods, ...methods,
@@ -236,6 +238,7 @@ export class Convo {
status: this.status, status: this.status,
items: this.getItems(), items: this.getItems(),
convo: this.convo!, convo: this.convo!,
relatedProfiles: this.relatedProfiles,
error: undefined, error: undefined,
...shared, ...shared,
...methods, ...methods,
@@ -246,6 +249,7 @@ export class Convo {
status: this.status, status: this.status,
items: this.getItems(), items: this.getItems(),
convo: this.convo!, convo: this.convo!,
relatedProfiles: this.relatedProfiles,
error: undefined, error: undefined,
...shared, ...shared,
...methods, ...methods,
@@ -1078,7 +1082,8 @@ export class Convo {
// continue queue processing // continue queue processing
await this.processPendingMessages() await this.processPendingMessages()
} catch (e: any) { } catch (err) {
const e = err as Error
this.handleSendMessageFailure(e) this.handleSendMessageFailure(e)
this.isProcessingPendingMessages = false this.isProcessingPendingMessages = false
} }
@@ -1177,7 +1182,8 @@ export class Convo {
this.commit() this.commit()
logger.debug(`sent ${this.pendingMessages.size} pending messages`, {}) logger.debug(`sent ${this.pendingMessages.size} pending messages`, {})
} catch (e: any) { } catch (err) {
const e = err as Error
this.handleSendMessageFailure(e) this.handleSendMessageFailure(e)
} }
} }
@@ -1231,25 +1237,18 @@ export class Convo {
type: 'message', type: 'message',
key: m.id, key: m.id,
message: m, message: m,
relatedProfiles: this.relatedProfiles,
nextMessage: null,
prevMessage: null,
}) })
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
items.unshift({ items.unshift({
type: 'deleted-message', type: 'deleted-message',
key: m.id, key: m.id,
message: m, message: m,
relatedProfiles: this.relatedProfiles,
nextMessage: null,
prevMessage: null,
}) })
} else if (ChatBskyConvoDefs.isSystemMessageView(m)) { } else if (ChatBskyConvoDefs.isSystemMessageView(m)) {
items.unshift({ items.unshift({
type: 'system-message', type: 'system-message',
key: m.id, key: m.id,
message: m, message: m,
relatedProfiles: this.relatedProfiles,
}) })
} }
}) })
@@ -1271,25 +1270,18 @@ export class Convo {
type: 'message', type: 'message',
key: m.id, key: m.id,
message: m, message: m,
relatedProfiles: this.relatedProfiles,
nextMessage: null,
prevMessage: null,
}) })
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
items.push({ items.push({
type: 'deleted-message', type: 'deleted-message',
key: m.id, key: m.id,
message: m, message: m,
relatedProfiles: this.relatedProfiles,
nextMessage: null,
prevMessage: null,
}) })
} else if (ChatBskyConvoDefs.isSystemMessageView(m)) { } else if (ChatBskyConvoDefs.isSystemMessageView(m)) {
items.push({ items.push({
type: 'system-message', type: 'system-message',
key: m.id, key: m.id,
message: m, message: m,
relatedProfiles: this.relatedProfiles,
}) })
} }
}) })
@@ -1310,9 +1302,6 @@ export class Convo {
did: this.senderUserDid, did: this.senderUserDid,
}, },
}, },
relatedProfiles: this.relatedProfiles,
nextMessage: null,
prevMessage: null,
failed: this.pendingMessageFailure !== null, failed: this.pendingMessageFailure !== null,
retry: retry:
this.pendingMessageFailure === 'recoverable' this.pendingMessageFailure === 'recoverable'
@@ -1334,53 +1323,12 @@ export class Convo {
}) })
} }
return items return items.filter(item => {
.filter(item => { if (isConvoItemMessage(item)) {
if (isConvoItemMessage(item)) { return !this.deletedMessages.has(item.message.id)
return !this.deletedMessages.has(item.message.id) }
} return true
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
})
} }
/** /**
+4 -28
View File
@@ -72,29 +72,11 @@ export type ConvoItem =
type: 'message' type: 'message'
key: string key: string
message: ChatBskyConvoDefs.MessageView message: ChatBskyConvoDefs.MessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
} }
| { | {
type: 'pending-message' type: 'pending-message'
key: string key: string
message: ChatBskyConvoDefs.MessageView message: ChatBskyConvoDefs.MessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
failed: boolean failed: boolean
/** /**
* Retry sending the message. If present, the message is in a failed state. * Retry sending the message. If present, the message is in a failed state.
@@ -105,21 +87,11 @@ export type ConvoItem =
type: 'deleted-message' type: 'deleted-message'
key: string key: string
message: ChatBskyConvoDefs.DeletedMessageView message: ChatBskyConvoDefs.DeletedMessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
} }
| { | {
type: 'system-message' type: 'system-message'
key: string key: string
message: ChatBskyConvoDefs.SystemMessageView message: ChatBskyConvoDefs.SystemMessageView
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
} }
| { | {
type: 'error' type: 'error'
@@ -172,6 +144,7 @@ export type ConvoStateReady = {
status: ConvoStatus.Ready status: ConvoStatus.Ready
items: ConvoItem[] items: ConvoItem[]
convo: ConvoWithDetails convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined error: undefined
isFetchingHistory: boolean isFetchingHistory: boolean
hasAllHistory: boolean hasAllHistory: boolean
@@ -186,6 +159,7 @@ export type ConvoStateBackgrounded = {
status: ConvoStatus.Backgrounded status: ConvoStatus.Backgrounded
items: ConvoItem[] items: ConvoItem[]
convo: ConvoWithDetails convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined error: undefined
isFetchingHistory: boolean isFetchingHistory: boolean
hasAllHistory: boolean hasAllHistory: boolean
@@ -200,6 +174,7 @@ export type ConvoStateSuspended = {
status: ConvoStatus.Suspended status: ConvoStatus.Suspended
items: ConvoItem[] items: ConvoItem[]
convo: ConvoWithDetails convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined error: undefined
isFetchingHistory: boolean isFetchingHistory: boolean
hasAllHistory: boolean hasAllHistory: boolean
@@ -228,6 +203,7 @@ export type ConvoStateDisabled = {
status: ConvoStatus.Disabled status: ConvoStatus.Disabled
items: ConvoItem[] items: ConvoItem[]
convo: ConvoWithDetails convo: ConvoWithDetails
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
error: undefined error: undefined
isFetchingHistory: boolean isFetchingHistory: boolean
hasAllHistory: boolean hasAllHistory: boolean