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

Profiles to React Queries #633

Closed
wants to merge 4 commits into from
Closed
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/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { Indicator } from "./Indicator";
import Picto from "./Picto/Picto";

type Props = {
export type AvatarProps = {
uri?: string | undefined;
size?: number | undefined;
style?: StyleProp<ImageStyle>;
Expand All @@ -36,7 +36,7 @@
name,
showIndicator,
invertColor,
}: Props) {
}: AvatarProps) {
const colorScheme = useColorScheme();
const styles = getStyles(colorScheme, size, invertColor || false);
const firstLetter = getFirstLetterForAvatar(name || "");
Expand Down Expand Up @@ -125,4 +125,4 @@
},
});

export default React.memo(Avatar);

Check warning on line 128 in components/Avatar.tsx

View workflow job for this annotation

GitHub Actions / lint

Prefer named exports
9 changes: 4 additions & 5 deletions components/ConversationListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ import Animated, {
runOnJS,
} from "react-native-reanimated";

import Avatar from "./Avatar";
import { ConversationContextMenu } from "./ConversationContextMenu";
import GroupAvatar from "./GroupAvatar";
import Picto from "./Picto/Picto";
import { showActionSheetWithOptions } from "./StateHandlers/ActionSheetStateHandler";
import { ConnectedAvatar } from "../containers/ConnectedAvatar";
import {
currentAccount,
useChatStore,
Expand Down Expand Up @@ -258,15 +258,14 @@ const ConversationListItem = memo(function ConversationListItem({
onConversationListScreen
/>
) : (
<Avatar
<ConnectedAvatar
size={AvatarSizes.conversationListItem}
style={styles.avatarWrapper}
uri={conversationPeerAvatar}
name={conversationName}
peerAddress={conversationPeerAddress!}
/>
);
}, [
conversationName,
conversationPeerAddress,
conversationPeerAvatar,
conversationTopic,
isGroupConversation,
Expand Down
23 changes: 23 additions & 0 deletions containers/ConnectedAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Avatar, { AvatarProps } from "@components/Avatar";
import { useProfileSocials } from "@hooks/useProfileSocials";
import { getPreferredAvatar, getPreferredName } from "@utils/profile";
import { FC } from "react";

interface ConnectedAvatarProps
extends Pick<
AvatarProps,
"size" | "style" | "color" | "showIndicator" | "invertColor"
> {
peerAddress: string;
}

export const ConnectedAvatar: FC<ConnectedAvatarProps> = ({
peerAddress,
...avatarProps
}) => {
const { data } = useProfileSocials(peerAddress);

const preferredAvatar = data ? getPreferredAvatar(data) : undefined;
const preferredName = data ? getPreferredName(data, peerAddress) : undefined;
return <Avatar uri={preferredAvatar} name={preferredName} {...avatarProps} />;
};
15 changes: 7 additions & 8 deletions containers/GroupPendingRequestsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { showActionSheetWithOptions } from "@components/StateHandlers/ActionSheetStateHandler";
import { TableViewPicto } from "@components/TableView/TableViewImage";
import { useCurrentAccount, useProfilesStore } from "@data/store/accountsStore";
import { useCurrentAccount } from "@data/store/accountsStore";
import { useGroupPendingRequests } from "@hooks/useGroupPendingRequests";
import { usePreferredNames } from "@hooks/usePreferredNames";
import { translate } from "@i18n";
import { useAddToGroupMutation } from "@queries/useAddToGroupMutation";
import { invalidatePendingJoinRequestsQuery } from "@queries/usePendingRequestsQuery";
import { actionSheetColors, textSecondaryColor } from "@styles/colors";
import { updateGroupJoinRequestStatus } from "@utils/api";
import { getPreferredName, getProfile } from "@utils/profile";

Check failure on line 11 in containers/GroupPendingRequestsTable.tsx

View workflow job for this annotation

GitHub Actions / lint

'getPreferredName' is defined but never used

Check failure on line 11 in containers/GroupPendingRequestsTable.tsx

View workflow job for this annotation

GitHub Actions / lint

'getProfile' is defined but never used
import { FC, useMemo } from "react";
import { StyleSheet, Text, useColorScheme, View } from "react-native";

Expand All @@ -26,20 +27,18 @@
const currentAccount = useCurrentAccount() as string;
const styles = useStyles();
const requests = useGroupPendingRequests(topic);
const profiles = useProfilesStore((s) => s.profiles);
const addresses = useMemo(() => requests.map((a) => a[0]), [requests]);
const preferredNames = usePreferredNames(addresses);
const { mutateAsync: addToGroup } = useAddToGroupMutation(
currentAccount,
topic
);
const tableViewItems = useMemo(() => {
const items: TableViewItemType[] = [];
requests.forEach((a) => {
requests.forEach((a, id) => {
const address = a[0];
const request = a[1];
const preferredName = getPreferredName(
getProfile(address, profiles)?.socials,
address
);
const preferredName = preferredNames[id];
items.push({
id: address,
title: preferredName,
Expand Down Expand Up @@ -101,7 +100,7 @@
addToGroup,
colorScheme,
currentAccount,
profiles,
preferredNames,
requests,
styles.adminText,
styles.tableViewRight,
Expand Down
12 changes: 10 additions & 2 deletions data/helpers/profiles/profilesUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setProfileSocialsQueryData } from "@queries/useProfileSocialsQuery";
import { getCleanAddress } from "@utils/evm/address";

import { getProfilesForAddresses } from "../../../utils/api";
Expand Down Expand Up @@ -31,7 +32,14 @@ export const updateProfilesForConvos = async (
updatedAt: now,
};
}
getProfilesStore(account).getState().setProfiles(socialsToDispatch);
for (const peerAddress in socialsToDispatch) {
setProfileSocialsQueryData(
account,
peerAddress,
socialsToDispatch[peerAddress].socials,
socialsToDispatch[peerAddress].updatedAt
);
}
}
};

Expand All @@ -42,7 +50,6 @@ export const refreshProfileForAddress = async (
const now = new Date().getTime();
const profilesByAddress = await getProfilesForAddresses([address]);
// Save profiles to db

getProfilesStore(account)
.getState()
.setProfiles({
Expand All @@ -51,6 +58,7 @@ export const refreshProfileForAddress = async (
updatedAt: now,
},
});
setProfileSocialsQueryData(account, address, profilesByAddress[address], now);
};

export const refreshProfilesIfNeeded = async (account: string) => {
Expand Down
14 changes: 13 additions & 1 deletion data/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import "reflect-metadata";

import { setProfileSocialsQueryData } from "@queries/useProfileSocialsQuery";
import logger from "@utils/logger";
import { getProfile } from "@utils/profile";

import { getRepository } from "./db";
import { Conversation } from "./db/entities/conversationEntity";
import { Message } from "./db/entities/messageEntity";
import { refreshProfilesIfNeeded } from "./helpers/profiles/profilesUpdate";
import { xmtpConversationFromDb } from "./mappers";
import { getChatStore, getProfilesStore } from "./store/accountsStore";
import { saveXmtpEnv, saveApiURI } from "../utils/sharedData";
import { refreshProfilesIfNeeded } from "./helpers/profiles/profilesUpdate";

const getTypeormBoolValue = (value: number) => value === 1;

Expand Down Expand Up @@ -76,6 +77,17 @@ export const loadDataToContext = async (account: string) => {
});

const profilesByAddress = getProfilesStore(account).getState().profiles;
for (const peerAddress in profilesByAddress) {
const profile = profilesByAddress[peerAddress];
if (profile?.socials && profile?.updatedAt) {
setProfileSocialsQueryData(
account,
peerAddress,
profile.socials,
profile.updatedAt
);
}
}
getChatStore(account)
.getState()
.setConversations(
Expand Down
8 changes: 8 additions & 0 deletions hooks/usePreferredName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getPreferredName } from "@utils/profile";

import { useProfileSocials } from "./useProfileSocials";

export const usePreferredName = (peerAddress: string) => {
const { data } = useProfileSocials(peerAddress);
return data ? getPreferredName(data, peerAddress) : peerAddress;
};
22 changes: 22 additions & 0 deletions hooks/usePreferredNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getPreferredName } from "@utils/profile";
import { useMemo } from "react";

import { useProfilesSocials } from "./useProfilesSocials";

/**
*
* @param peerAddress Multiple peer addresses to get their socials
* @returns array of preferred names or the address if not found
*/
export const usePreferredNames = (peerAddresses: string[]) => {
const data = useProfilesSocials(peerAddresses);
const names = useMemo(() => {
// Not sure how performant this will be, or if we can safely rely on the index
// If we can't, we should probably use a Map instead
return data.map(({ data: socials }, index) => {
const peerAddress = peerAddresses[index];
return socials ? getPreferredName(socials, peerAddress) : peerAddress;
});
}, [data, peerAddresses]);
return names;
};
7 changes: 7 additions & 0 deletions hooks/useProfileSocials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useCurrentAccount } from "@data/store/accountsStore";
import { useProfileSocialsQuery } from "@queries/useProfileSocialsQuery";

export const useProfileSocials = (peerAddress: string) => {
const currentAccount = useCurrentAccount();
return useProfileSocialsQuery(currentAccount!, peerAddress);
};
12 changes: 12 additions & 0 deletions hooks/useProfilesSocials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useCurrentAccount } from "@data/store/accountsStore";
import { useProfileSocialsQueries } from "@queries/useProfileSocialsQuery";

/**
*
* @param peerAddresses Use multiple peer addresses to get their socials
* @returns
*/
export const useProfilesSocials = (peerAddresses: string[]) => {
const currentAccount = useCurrentAccount();
return useProfileSocialsQueries(currentAccount!, peerAddresses);
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@xmtp/proto": "^3.60.0",
"@xmtp/react-native-sdk": "^2.6.4",
"@xmtp/xmtp-js": "11.5.0",
"@yornaath/batshit": "^0.10.1",
"amazon-cognito-identity-js": "^6.3.12",
"axios": "^1.2.1",
"babel-plugin-transform-remove-console": "^6.9.4",
Expand Down
8 changes: 8 additions & 0 deletions queries/QueryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export enum QueryKeys {
GROUP_INVITE = "groupInvite",
GROUP_JOIN_REQUEST = "groupJoinRequest",
PENDING_JOIN_REQUESTS = "pendingJoinRequests",

// Profiles
PROFILE_SOCIALS = "profileSocials",
}

export const groupsQueryKey = (account: string) => [QueryKeys.GROUPS, account];
Expand Down Expand Up @@ -127,3 +130,8 @@ export const pendingJoinRequestsQueryKey = (account: string) => [
QueryKeys.PENDING_JOIN_REQUESTS,
account,
];

export const profileSocialsQueryKey = (
account: string,
peerAddress: string
) => [QueryKeys.PROFILE_SOCIALS, account, peerAddress];
Comment on lines +133 to +137
Copy link
Collaborator

Choose a reason for hiding this comment

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

Curious, why did you choose to add all keys into 1 file? Just really curious. I'm not doing that but maybe it's the best practice. What I do is put each keys into it's own file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just kinda did it one day, but I think how we are structuring folders by feature it probably makes more sense to split them into the separate files

84 changes: 84 additions & 0 deletions queries/useProfileSocialsQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ProfileSocials } from "@data/store/profilesStore";
import { useQueries, useQuery } from "@tanstack/react-query";
import { getProfilesForAddresses } from "@utils/api";
import {
create,
windowedFiniteBatchScheduler,
indexedResolver,
} from "@yornaath/batshit";

import { profileSocialsQueryKey } from "./QueryKeys";
import { queryClient } from "./queryClient";

const profileSocials = create({
fetcher: async (addresses: string[]) => {
const data = await getProfilesForAddresses(addresses);
return data;
},
resolver: indexedResolver(),
scheduler: windowedFiniteBatchScheduler({
windowMs: 10,
maxBatchSize: 150,
}),
});
Comment on lines +13 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think react-query handle request deduplication

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 does, but this helps with batching all requests
Example I am fetching profile A, B, C it will do all of those requests
Docs: https://tanstack.com/query/v4/docs/framework/react/community/batching-requests-using-bathshit#implementation

Copy link
Collaborator

Choose a reason for hiding this comment

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

OH very nice yeah makes sense didn't know about this


const fetchProfileSocials = async (peerAddress: string) => {
const data = await profileSocials.fetch(peerAddress);
return data;
};

const profileSocialesQueryConfig = (account: string, peerAddress: string) => ({
queryKey: profileSocialsQueryKey(account, peerAddress),
queryFn: () => fetchProfileSocials(peerAddress),
enabled: !!account,
// Store for 30 days
gcTime: 1000 * 60 * 60 * 24 * 30,
refetchIntervalInBackground: false,
refetchOnWindowFocus: false,
// We really just want a 24 hour cache here
// And automatic retries if there was an error fetching
refetchOnMount: false,
staleTime: 1000 * 60 * 60 * 24,
});

export const useProfileSocialsQuery = (
account: string,
peerAddress: string
) => {
return useQuery(profileSocialesQueryConfig(account, peerAddress));
};

export const useProfileSocialsQueries = (
account: string,
peerAddresses: string[]
) => {
return useQueries({
queries: peerAddresses.map((peerAddress) =>
profileSocialesQueryConfig(account, peerAddress)
),
});
};

export const fetchProfileSocialsQuery = (
account: string,
peerAddress: string
) => {
return queryClient.fetchQuery(
profileSocialesQueryConfig(account, peerAddress)
);
};

export const setProfileSocialsQueryData = (
account: string,
peerAddress: string,
data: ProfileSocials,
updatedAt?: number
) => {
return queryClient.setQueryData(
profileSocialsQueryKey(account, peerAddress),
data,
{
updatedAt,
}
);
};
9 changes: 2 additions & 7 deletions screens/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Picto from "@components/Picto/Picto";
import { ConnectedAvatar } from "@containers/ConnectedAvatar";
import { useDisconnectActionSheet } from "@hooks/useDisconnectActionSheet";
import { useShouldShowErrored } from "@hooks/useShouldShowErrored";
import { translate } from "@i18n";
Expand Down Expand Up @@ -43,7 +44,6 @@ import { ConversationNavParams } from "./Navigation/ConversationNav";
import { NavigationParamList } from "./Navigation/Navigation";
import { useIsSplitScreen } from "./Navigation/navHelpers";
import ActivityIndicator from "../components/ActivityIndicator/ActivityIndicator";
import Avatar from "../components/Avatar";
import { showActionSheetWithOptions } from "../components/StateHandlers/ActionSheetStateHandler";
import TableView, {
TableViewItemType,
Expand Down Expand Up @@ -78,7 +78,6 @@ import {
requestPushNotificationsPermissions,
} from "../utils/notifications";
import {
getPreferredAvatar,
getPreferredName,
getProfile,
getPreferredUsername,
Expand Down Expand Up @@ -600,11 +599,7 @@ export default function ProfileScreen({
style={styles.profile}
contentContainerStyle={styles.profileContent}
>
<Avatar
uri={getPreferredAvatar(socials)}
style={styles.avatar}
name={getPreferredName(socials, peerAddress)}
/>
<ConnectedAvatar peerAddress={peerAddress} style={styles.avatar} />
<Text style={styles.title}>{getPreferredName(socials, peerAddress)}</Text>
{isMyProfile && shouldShowError && (
<View style={styles.errorContainer}>
Expand Down
Loading
Loading