Skip to content

Commit

Permalink
fetch tokens while syncing (web & mobile) (#2431)
Browse files Browse the repository at this point in the history
* fetch tokens while syncing mobile & web

* run prettier

* add boundary web + fix issue with refetch mobile

* add loader when automatic fetching

* add tokens length check

---------

Co-authored-by: Robinnnnn <[email protected]>
  • Loading branch information
pvicensSpacedev and Robinnnnn authored Apr 26, 2024
1 parent 0f5ad13 commit d8bf5fc
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 93 deletions.
28 changes: 14 additions & 14 deletions apps/mobile/src/contexts/TokenStateManagerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,14 @@ export function TokenStateManagerProvider({ children }: PropsWithChildren) {
const environment = useRelayEnvironment();
const FragmentResource = getFragmentResourceForEnvironment(environment);
const incrementTokenRetryKey = useCallback(
(tokenId: string) => {
(tokenIdentifiers: string | string[]) => {
addBreadcrumb({
message: 'Trying to clear the Relay FragmentResource cache',
level: 'info',
});

// Wrapping this in a try catch since we have no idea
// if Relay wil introduce a breaking change here.
// This was copy-pasted from the web `NftErrorContext.tsx`
const tokenIdsArray = Array.isArray(tokenIdentifiers) ? tokenIdentifiers : [tokenIdentifiers];

try {
FragmentResource._cache._map.clear();
} catch (e) {
Expand All @@ -146,13 +145,16 @@ export function TokenStateManagerProvider({ children }: PropsWithChildren) {

setTokens((previous) => {
const next = { ...previous };
const token = { ...(next[tokenId] ?? defaultTokenState()) };
token.isFailed = false;
token.isLoading = false;
token.isPolling = false;
token.refreshingMetadata = false;
token.retryKey++;
next[tokenId] = token;
tokenIdsArray.forEach((tokenId) => {
const token = { ...(next[tokenId] ?? defaultTokenState()) };
token.isFailed = false;
token.isLoading = false;
token.isPolling = false;
token.refreshingMetadata = false;
token.retryKey++;
next[tokenId] = token;
});

return next;
});
},
Expand Down Expand Up @@ -217,9 +219,7 @@ export function TokenStateManagerProvider({ children }: PropsWithChildren) {
TokenStateManagerContextType['clearTokenFailureState']
>(
(tokenIds: string[]) => {
for (const tokenId of tokenIds) {
incrementTokenRetryKey(tokenId);
}
incrementTokenRetryKey(tokenIds);
},
[incrementTokenRetryKey]
);
Expand Down
72 changes: 60 additions & 12 deletions apps/mobile/src/screens/NftSelectorScreen/NftSelectorPickerGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { ResizeMode } from 'expo-av';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { View, ViewProps } from 'react-native';
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
import { useFragment, useLazyLoadQuery } from 'react-relay';
import { graphql } from 'relay-runtime';
import { useFragment, useLazyLoadQuery, useRelayEnvironment } from 'react-relay';
import { fetchQuery, graphql } from 'relay-runtime';

import { TokenFailureBoundary } from '~/components/Boundaries/TokenFailureBoundary/TokenFailureBoundary';
import { Button } from '~/components/Button';
Expand All @@ -27,6 +27,7 @@ import {
NftSelectorPickerGridTokensFragment$data,
NftSelectorPickerGridTokensFragment$key,
} from '~/generated/NftSelectorPickerGridTokensFragment.graphql';
import { NftSelectorPickerGridTokensQuery } from '~/generated/NftSelectorPickerGridTokensQuery.graphql';
import { LoginStackNavigatorProp } from '~/navigation/types';
import {
NetworkChoice,
Expand Down Expand Up @@ -89,7 +90,7 @@ export function NftSelectorPickerGrid({
);
const tokenRefs = removeNullValues(query.viewer?.user?.tokens);

const tokens = useFragment<NftSelectorPickerGridTokensFragment$key>(
const tokensData = useFragment<NftSelectorPickerGridTokensFragment$key>(
graphql`
fragment NftSelectorPickerGridTokensFragment on Token @relay(plural: true) {
dbid
Expand Down Expand Up @@ -120,12 +121,55 @@ export function NftSelectorPickerGrid({
tokenRefs
);

const { isSyncing, isSyncingCreatedTokens } = useSyncTokensActions();

const relayEnvironment = useRelayEnvironment();

useEffect(() => {
let intervalId: number | undefined;

if (isSyncing) {
const fetchTokens = async () => {
const tokensQuery = graphql`
query NftSelectorPickerGridTokensQuery {
viewer {
... on Viewer {
user {
tokens {
dbid
creationTime
...NftSelectorPickerGridTokensFragment
}
}
}
}
}
`;

await fetchQuery<NftSelectorPickerGridTokensQuery>(
relayEnvironment,
tokensQuery,
{}
).toPromise();
};

fetchTokens();
intervalId = window.setInterval(fetchTokens, 5000);
}

return () => {
if (intervalId !== undefined) {
clearInterval(intervalId);
}
};
}, [isSyncing, relayEnvironment]);

const { openManageWallet } = useManageWalletActions();

// [GAL-4202] this logic could be consolidated across web editor + web selector + mobile selector
// but also don't overdo it if there's sufficient differentiation between web and mobile UX
const filteredTokens = useMemo(() => {
return tokens
return tokensData
.filter((token) => {
const isSpam = token.definition?.contract?.isSpam || token.isSpamByUser;

Expand Down Expand Up @@ -154,7 +198,7 @@ export function NftSelectorPickerGrid({
searchCriteria.networkFilter,
searchCriteria.ownerFilter,
searchCriteria.searchQuery,
tokens,
tokensData,
]);

const sortedTokens = useMemo(() => {
Expand Down Expand Up @@ -220,8 +264,6 @@ export function NftSelectorPickerGrid({
return groups;
}, [sortedTokens]);

const { isSyncing, isSyncingCreatedTokens } = useSyncTokensActions();

// TODO: this logic is messy and shared with web; should be refactored
const handleRefresh = useCallback(() => {
if (!ownsWalletFromSelectedChainFamily) {
Expand All @@ -245,12 +287,16 @@ export function NftSelectorPickerGrid({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchCriteria.networkFilter, searchCriteria.ownerFilter]);

type Row = { groups: Group[] };
type Row = { groups: Group[]; id: string };
const rows = useMemo(() => {
const rows: Row[] = [];

for (let i = 0; i < Object.keys(groups).length; i += 3) {
rows.push({ groups: Object.values(groups).slice(i, i + 3) });
const groupSlice = Object.values(groups).slice(i, i + 3);
const sliceKey = groupSlice.map((group) => group.address).join('-');
const id = `row-${sliceKey}`;

rows.push({ groups: groupSlice, id });
}

return rows;
Expand Down Expand Up @@ -298,9 +344,6 @@ export function NftSelectorPickerGrid({

const isRefreshing = isSyncing || isSyncingCreatedTokens;

if (isRefreshing) {
return <NftSelectorLoadingSkeleton />;
}
const user = query?.viewer?.user;
if (!user?.primaryWallet) {
return (
Expand All @@ -320,6 +363,10 @@ export function NftSelectorPickerGrid({
);
}

if (isRefreshing && !rows.length) {
return <NftSelectorLoadingSkeleton />;
}

if (!rows.length) {
return (
<View className="flex flex-col flex-1 pt-16" style={style}>
Expand All @@ -333,6 +380,7 @@ export function NftSelectorPickerGrid({
return (
<View className="flex flex-col flex-1" style={style}>
<FlashList
keyExtractor={(item) => item.id}
renderItem={renderItem}
data={rows}
estimatedItemSize={200}
Expand Down
78 changes: 35 additions & 43 deletions apps/web/src/components/NftSelector/NftSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Suspense, useCallback, useEffect, useMemo } from 'react';
import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';
import { graphql, useLazyLoadQuery } from 'react-relay';
import { useSyncCreatedTokensForExistingContract } from 'src/hooks/api/tokens/useSyncCreatedTokensForExistingContract';
import styled from 'styled-components';

import { usePostComposerContext } from '~/contexts/postComposer/PostComposerContext';
import { NftSelectorQuery } from '~/generated/NftSelectorQuery.graphql';
import { NftSelectorViewerFragment$key } from '~/generated/NftSelectorViewerFragment.graphql';
import useSyncTokens from '~/hooks/api/tokens/useSyncTokens';
import { ChevronLeftIcon } from '~/icons/ChevronLeftIcon';
import { RefreshIcon } from '~/icons/RefreshIcon';
Expand Down Expand Up @@ -56,55 +55,49 @@ function NftSelectorInner({ onSelectToken, headerText, preSelectedContract, even
viewer {
... on Viewer {
...NftSelectorViewerFragment
}
}
...NftSelectorTokensQueryFragment
}
`,
{}
);
user {
dbid
tokens(ownershipFilter: [Creator, Holder]) {
__typename
const viewer = useFragment<NftSelectorViewerFragment$key>(
graphql`
fragment NftSelectorViewerFragment on Viewer {
user {
dbid
tokens(ownershipFilter: [Creator, Holder]) {
__typename
dbid
creationTime
definition {
name
chain
contract {
dbid
name
isSpam
creationTime
definition {
name
chain
contract {
dbid
name
isSpam
}
}
isSpamByUser
ownerIsHolder
ownerIsCreator
...useTokenSearchResultsFragment
...NftSelectorTokensFragment
# Needed for when we select a token, we want to have this already in the cache
# eslint-disable-next-line relay/must-colocate-fragment-spreads
...PostComposerTokenFragment
}
}
isSpamByUser
ownerIsHolder
ownerIsCreator
...useTokenSearchResultsFragment
...NftSelectorTokensFragment
# Needed for when we select a token, we want to have this already in the cache
# eslint-disable-next-line relay/must-colocate-fragment-spreads
...PostComposerTokenFragment
}
}
...NftSelectorTokensQueryFragment
}
`,
query.viewer
{}
);

const tokens = useMemo(() => removeNullValues(viewer?.user?.tokens), [viewer?.user?.tokens]);
const tokens = useMemo(
() => removeNullValues(query.viewer?.user?.tokens),
[query.viewer?.user?.tokens]
);
const { searchQuery, setSearchQuery, tokenSearchResults, isSearching } = useTokenSearchResults<
(typeof tokens)[0]
>({
Expand Down Expand Up @@ -217,7 +210,7 @@ function NftSelectorInner({ onSelectToken, headerText, preSelectedContract, even

const ownsWalletFromSelectedChainFamily = doesUserOwnWalletFromChainFamily(network, query);

const isRefreshDisabledAtUserLevel = isRefreshDisabledForUser(viewer?.user?.dbid ?? '');
const isRefreshDisabledAtUserLevel = isRefreshDisabledForUser(query?.viewer?.user?.dbid ?? '');
const refreshDisabled =
isRefreshDisabledAtUserLevel || !ownsWalletFromSelectedChainFamily || isLocked;

Expand Down Expand Up @@ -358,7 +351,6 @@ function NftSelectorInner({ onSelectToken, headerText, preSelectedContract, even

<NftSelectorTokens
selectedFilter={filterType}
isLocked={isLocked}
tokenRefs={tokensToDisplay}
selectedContractAddress={selectedContract?.address ?? null}
onSelectContract={handleSelectContract}
Expand Down
7 changes: 0 additions & 7 deletions apps/web/src/components/NftSelector/NftSelectorTokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import { Chain } from '~/shared/utils/chains';
import CreatorSupportAnnouncement from '../Announcement/CreatorSupportAnnouncement';
import { VStack } from '../core/Spacer/Stack';
import { NftSelectorContractType } from './NftSelector';
import { NftSelectorLoadingView } from './NftSelectorLoadingView';
import { NftSelectorView } from './NftSelectorView';

type Props = {
selectedFilter: string;
isLocked: boolean;
tokenRefs: NftSelectorTokensFragment$key;
selectedContractAddress: string | null;
onSelectContract: (contract: NftSelectorContractType) => void;
Expand All @@ -31,7 +29,6 @@ type Props = {

export default function NftSelectorTokens({
selectedFilter,
isLocked,
selectedContractAddress,
onSelectContract,
onSelectToken,
Expand Down Expand Up @@ -85,10 +82,6 @@ export default function NftSelectorTokens({
);
}

if (isLocked) {
return <NftSelectorLoadingView />;
}

return (
<NftSelectorView
tokenRefs={tokens}
Expand Down
Loading

0 comments on commit d8bf5fc

Please sign in to comment.