Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Profile Caching #1441

Merged
merged 2 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/StateHandlers/HydrationStateHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this out as it's a prefetch, we don't want to await it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've put it there and we might need some refactoring for this but I think having the InboxId available is a must before showing the content to the user. Since we use it so much.

But again, with the refactor of accounts maybe that will be somewhere else or etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be cached better now, so I think it will be a better experience, where previously it was not cached


const errors = results.filter(
(result) => result.status === "rejected"
Expand Down
4 changes: 3 additions & 1 deletion components/V3DMListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion components/V3GroupConversationListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
3 changes: 2 additions & 1 deletion features/conversation-list/hooks/useMessageIsUnread.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand Down
24 changes: 17 additions & 7 deletions queries/useConversationMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<ConversationMessagesQueryData> {
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
Expand Down
25 changes: 21 additions & 4 deletions queries/useInboxProfileSocialsQuery.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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(),
Expand All @@ -34,7 +37,10 @@ const profileSocials = create({
}),
});

const fetchInboxProfileSocials = async (account: string, inboxId: InboxId) => {
const fetchInboxProfileSocials = async (
account: string,
inboxId: InboxId
): Promise<IProfileSocials[] | null> => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type should be inferred no?

const data = await profileSocials.fetch(inboxId);

const key = inboxProfileSocialsQueryStorageKey(account, inboxId);
Expand Down Expand Up @@ -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 => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type should be inferred no?

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 = (
Expand Down
18 changes: 15 additions & 3 deletions queries/useProfileSocialsQuery.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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 => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type should be inferred no?

if (mmkv.contains(profileSocialsQueryStorageKey(account, peerAddress))) {
const data = JSON.parse(
mmkv.getString(profileSocialsQueryStorageKey(account, peerAddress))!
) as ProfileSocialsData;
return data;
}
},
initialDataUpdatedAt: 0,
// persister: reactQueryPersister,
});

Expand Down Expand Up @@ -96,7 +108,7 @@ export const fetchProfileSocialsQuery = (
account: string,
peerAddress: string
) => {
return queryClient.fetchQuery<ProfileSocialsData>(
return queryClient.fetchQuery<IProfileSocials | null>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this I love having something like in useConversationMessages


export type ConversationMessagesQueryData = Awaited<
  ReturnType<typeof conversationMessagesQueryFn>
>;

and reusing ConversationMessagesQueryData everywhere we use queryClient...

profileSocialsQueryConfig(account, peerAddress)
);
};
Expand Down
Loading