diff --git a/components/StateHandlers/HydrationStateHandler.tsx b/components/StateHandlers/HydrationStateHandler.tsx index 44c5d30ad..e9f67a58e 100644 --- a/components/StateHandlers/HydrationStateHandler.tsx +++ b/components/StateHandlers/HydrationStateHandler.tsx @@ -33,10 +33,10 @@ export default function HydrationStateHandler() { ); const results = await Promise.allSettled([ - getXmtpClient(account), + // This will handle creating the client and setting the conversation list from persistence fetchPersistedConversationListQuery({ account }), - prefetchInboxIdQuery({ account }), ]); + prefetchInboxIdQuery({ account }); const errors = results.filter( (result) => result.status === "rejected" diff --git a/components/V3DMListItem.tsx b/components/V3DMListItem.tsx index 6e75f8ad5..7d2afdcc3 100644 --- a/components/V3DMListItem.tsx +++ b/components/V3DMListItem.tsx @@ -27,6 +27,7 @@ import { useToggleReadStatus } from "../features/conversation-list/hooks/useTogg import Avatar from "./Avatar"; import { ContextMenuIcon, ContextMenuItem } from "./ContextMenuItems"; import { ConversationListItemDumb } from "./ConversationListItem/ConversationListItemDumb"; +import { prefetchConversationMessages } from "@/queries/useConversationMessages"; type V3DMListItemProps = { conversation: DmWithCodecsType; @@ -162,10 +163,11 @@ export const V3DMListItem = ({ conversation }: V3DMListItemProps) => { }, [avatarUri, preferredName]); const onPress = useCallback(() => { + prefetchConversationMessages(currentAccount, topic); navigate("Conversation", { topic: topic, }); - }, [topic]); + }, [topic, currentAccount]); const onLeftSwipe = useCallback(() => { const translation = ref.current?.state.rowTranslation; diff --git a/components/V3GroupConversationListItem.tsx b/components/V3GroupConversationListItem.tsx index 58ab62db5..3371e1ee0 100644 --- a/components/V3GroupConversationListItem.tsx +++ b/components/V3GroupConversationListItem.tsx @@ -10,7 +10,7 @@ import { saveTopicsData } from "@utils/api"; import { getMinimalDate } from "@utils/date"; import { Haptics } from "@utils/haptics"; import { navigate } from "@utils/navigation"; -import { RefObject, useCallback, useEffect, useMemo, useRef } from "react"; +import { RefObject, useCallback, useMemo, useRef } from "react"; import { useColorScheme } from "react-native"; import { Swipeable } from "react-native-gesture-handler"; import { runOnJS } from "react-native-reanimated"; diff --git a/features/conversation-list/hooks/useMessageIsUnread.ts b/features/conversation-list/hooks/useMessageIsUnread.ts index 465d94d9d..5658659e5 100644 --- a/features/conversation-list/hooks/useMessageIsUnread.ts +++ b/features/conversation-list/hooks/useMessageIsUnread.ts @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { ChatStoreType } from "@data/store/chatStore"; import { DecodedMessageWithCodecsType } from "@/utils/xmtpRN/client.types"; -import { useChatStore, useCurrentAccount } from "@data/store/accountsStore"; +import { useChatStore } from "@data/store/accountsStore"; import { useSelect } from "@data/store/storeHelpers"; import { normalizeTimestamp } from "@/utils/date"; import { getCurrentUserAccountInboxId } from "@/hooks/use-current-account-inbox-id"; @@ -25,6 +25,7 @@ export const useConversationIsUnread = ({ return useMemo(() => { // No message means no unread status if (!lastMessage) return false; + if (!topicsData[topic]) return false; // Check if status is unread if (topicsData[topic]?.status === "unread") return true; diff --git a/queries/useConversationMessages.ts b/queries/useConversationMessages.ts index 3c435e5c1..b8952bf16 100644 --- a/queries/useConversationMessages.ts +++ b/queries/useConversationMessages.ts @@ -29,26 +29,35 @@ export const conversationMessagesQueryFn = async ( conversation: ConversationWithCodecsType, options?: MessagesOptions ) => { + const start = performance.now(); logger.info("[useConversationMessages] queryFn fetching messages..."); if (!conversation) { throw new Error("Conversation not found in conversationMessagesQueryFn"); } const messages = await conversation.messages(options); + const end = performance.now(); logger.info( - `[useConversationMessages] queryFn fetched ${messages.length} messages` + `[useConversationMessages] queryFn fetched ${messages.length} messages in ${end - start}ms` ); - return processMessages({ messages }); + const processingStart = performance.now(); + const processedMessages = processMessages({ messages }); + const processingEnd = performance.now(); + logger.info( + `[useConversationMessages] queryFn processed ${messages.length} messages in ${processingEnd - processingStart}ms` + ); + return processedMessages; }; const conversationMessagesByTopicQueryFn = async ( account: string, - topic: ConversationTopic + topic: ConversationTopic, + includeSync: boolean = true ) => { logger.info("[useConversationMessages] queryFn fetching messages by topic"); const conversation = await getConversationByTopicByAccount({ account, topic, - includeSync: true, + includeSync, }); if (!conversation) { throw new Error( @@ -117,19 +126,20 @@ export const prefetchConversationMessages = async ( topic: ConversationTopic ) => { return queryClient.prefetchQuery( - getConversationMessagesQueryOptions(account, topic) + getConversationMessagesQueryOptions(account, topic, false) ); }; function getConversationMessagesQueryOptions( account: string, - topic: ConversationTopic + topic: ConversationTopic, + includeSync: boolean = true ): UseQueryOptions { const conversation = getConversationQueryData({ account, topic }); return { queryKey: conversationMessagesQueryKey(account, topic), queryFn: () => { - return conversationMessagesByTopicQueryFn(account, topic); + return conversationMessagesByTopicQueryFn(account, topic, includeSync); }, enabled: !!conversation, refetchOnMount: true, // Just for now because messages are very important and we want to make sure we have all of them diff --git a/queries/useInboxProfileSocialsQuery.ts b/queries/useInboxProfileSocialsQuery.ts index a447c4909..0e3bf4290 100644 --- a/queries/useInboxProfileSocialsQuery.ts +++ b/queries/useInboxProfileSocialsQuery.ts @@ -1,5 +1,5 @@ import { IProfileSocials } from "@/features/profiles/profile-types"; -import { useQueries, useQuery } from "@tanstack/react-query"; +import { QueryKey, useQueries, useQuery } from "@tanstack/react-query"; import { getProfilesForInboxIds } from "@utils/api"; import { create, @@ -11,7 +11,10 @@ import { queryClient } from "./queryClient"; import { InboxId } from "@xmtp/react-native-sdk"; import mmkv from "@/utils/mmkv"; -const profileSocialsQueryKey = (account: string, peerAddress: string) => [ +const profileSocialsQueryKey = ( + account: string, + peerAddress: string +): QueryKey => [ "inboxProfileSocials", account?.toLowerCase(), peerAddress?.toLowerCase(), @@ -34,7 +37,10 @@ const profileSocials = create({ }), }); -const fetchInboxProfileSocials = async (account: string, inboxId: InboxId) => { +const fetchInboxProfileSocials = async ( + account: string, + inboxId: InboxId +): Promise => { const data = await profileSocials.fetch(inboxId); const key = inboxProfileSocialsQueryStorageKey(account, inboxId); @@ -63,7 +69,18 @@ const inboxProfileSocialsQueryConfig = ( // And automatic retries if there was an error fetching refetchOnMount: false, staleTime: 1000 * 60 * 60 * 24, - // persister: reactQueryPersister, + initialData: (): IProfileSocials[] | null | undefined => { + if (!account || !inboxId) { + return undefined; + } + if (mmkv.contains(inboxProfileSocialsQueryStorageKey(account, inboxId))) { + const data = JSON.parse( + mmkv.getString(inboxProfileSocialsQueryStorageKey(account, inboxId))! + ) as IProfileSocials[]; + return data; + } + }, + initialDataUpdatedAt: 0, }); export const useInboxProfileSocialsQuery = ( diff --git a/queries/useProfileSocialsQuery.ts b/queries/useProfileSocialsQuery.ts index 669a27aa2..fe03575e5 100644 --- a/queries/useProfileSocialsQuery.ts +++ b/queries/useProfileSocialsQuery.ts @@ -1,5 +1,5 @@ import { IProfileSocials } from "@/features/profiles/profile-types"; -import { useQueries, useQuery } from "@tanstack/react-query"; +import { QueryKey, useQueries, useQuery } from "@tanstack/react-query"; import { getProfilesForAddresses } from "@utils/api"; import { create, @@ -12,7 +12,10 @@ import mmkv from "@/utils/mmkv"; type ProfileSocialsData = IProfileSocials | null | undefined; -const profileSocialsQueryKey = (account: string, peerAddress: string) => [ +const profileSocialsQueryKey = ( + account: string, + peerAddress: string +): QueryKey => [ "profileSocials", account?.toLowerCase(), // Typesafe because there's a lot of account! usage @@ -62,6 +65,15 @@ const profileSocialsQueryConfig = (account: string, peerAddress: string) => ({ // And automatic retries if there was an error fetching refetchOnMount: false, staleTime: 1000 * 60 * 60 * 24, + initialData: (): ProfileSocialsData => { + if (mmkv.contains(profileSocialsQueryStorageKey(account, peerAddress))) { + const data = JSON.parse( + mmkv.getString(profileSocialsQueryStorageKey(account, peerAddress))! + ) as ProfileSocialsData; + return data; + } + }, + initialDataUpdatedAt: 0, // persister: reactQueryPersister, }); @@ -96,7 +108,7 @@ export const fetchProfileSocialsQuery = ( account: string, peerAddress: string ) => { - return queryClient.fetchQuery( + return queryClient.fetchQuery( profileSocialsQueryConfig(account, peerAddress) ); };