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

Migrate dapps query to createQueryStore #6385

Open
wants to merge 3 commits into
base: @christian/remote-rainbow-store
Choose a base branch
from
Open
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
11 changes: 5 additions & 6 deletions src/components/DappBrowser/Homepage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { memo, useCallback, useLayoutEffect, useMemo, useRef, useState }
import { ScrollView, StyleSheet, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle } from 'react-native-reanimated';
import { triggerHaptics } from 'react-native-turbo-haptics';
import { ButtonPressAnimation } from '@/components/animations';
import {
Bleed,
Expand Down Expand Up @@ -30,9 +31,8 @@ import { getDappHost } from './handleProviderRequest';
import { uniqBy } from 'lodash';
import { DEVICE_WIDTH } from '@/utils/deviceUtils';
import { EXTRA_WEBVIEW_HEIGHT, WEBVIEW_HEIGHT } from './Dimensions';
import { useDapps } from '@/resources/metadata/dapps';
import { useBrowserDappsStore } from '@/resources/metadata/dapps';
import { analyticsV2 } from '@/analytics';
import haptics from '@/utils/haptics';
import * as i18n from '@/languages';
import { useBrowserStore } from '@/state/browser/browserStore';
import { DndProvider, Draggable, DraggableGrid, DraggableGridProps, UniqueIdentifier } from '../drag-and-drop';
Expand Down Expand Up @@ -310,7 +310,6 @@ const Card = memo(function Card({
}) {
const { isDarkMode } = useColorMode();

const { dapps } = useDapps();
const isFavorite = useFavoriteDappsStore(state => state.isFavorite(site.url || ''));
const addFavorite = useFavoriteDappsStore(state => state.addFavorite);
const removeFavorite = useFavoriteDappsStore(state => state.removeFavorite);
Expand Down Expand Up @@ -345,7 +344,7 @@ const Card = memo(function Card({

const onPressMenuItem = useCallback(
async ({ nativeEvent: { actionKey } }: { nativeEvent: { actionKey: 'favorite' | 'remove' } }) => {
haptics.selection();
triggerHaptics('selection');
if (actionKey === 'favorite') {
handleFavoritePress();
} else if (actionKey === 'remove') {
Expand Down Expand Up @@ -385,14 +384,14 @@ const Card = memo(function Card({
const iconUrl = site.image;
const url = dappUrl.startsWith('http') ? dappUrl : `https://${dappUrl}`;
const host = new URL(url).hostname;
const dappOverride = useBrowserDappsStore.getState().findDappByHostname(host);
// 👇 TODO: Remove this once the Uniswap logo in the dapps metadata is fixed
const isUniswap = host === 'uniswap.org' || host.endsWith('.uniswap.org');
const dappOverride = dapps.find(dapp => dapp.urlDisplay === host);
if (dappOverride?.iconUrl && !isUniswap) {
return dappOverride.iconUrl;
}
return iconUrl;
}, [dapps, site.image, site.url]);
}, [site.image, site.url]);

return (
<Box>
Expand Down
10 changes: 4 additions & 6 deletions src/components/DappBrowser/search/results/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { SPRING_CONFIGS } from '@/components/animations/animationConfigs';
import { EasingGradient } from '@/components/easing-gradient/EasingGradient';
import { Bleed, Box, Inline, Inset, Stack, Text, TextIcon, useColorMode, useForegroundColor } from '@/design-system';
import * as i18n from '@/languages';
import { Dapp, DappsContextProvider, useDappsContext } from '@/resources/metadata/dapps';
import { Dapp, useBrowserDappsStore } from '@/resources/metadata/dapps';
import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants';
import deviceUtils, { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils';
import { useBrowserContext } from '../../BrowserContext';
Expand Down Expand Up @@ -268,9 +268,7 @@ export const SearchResults = React.memo(function SearchResults({ goToUrl }: { go
</Animated.View>
</Animated.View>

<DappsContextProvider>
<DappsDataSync isFocused={isFocused} searchQuery={searchQuery} searchResults={searchResults} />
</DappsContextProvider>
<DappsDataSync isFocused={isFocused} searchQuery={searchQuery} searchResults={searchResults} />
</>
);
});
Expand All @@ -284,7 +282,7 @@ const DappsDataSync = ({
searchQuery: SharedValue<string>;
searchResults: SharedValue<Dapp[]>;
}) => {
const { dapps } = useDappsContext();
const dapps = useBrowserDappsStore(state => state.dapps);

useAnimatedReaction(
() => searchQuery.value.trim(),
Expand All @@ -305,7 +303,7 @@ const DappsDataSync = ({
});
}
},
[]
[dapps]
);

return null;
Expand Down
137 changes: 61 additions & 76 deletions src/resources/metadata/dapps.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,85 @@
import { formatUrl } from '@/components/DappBrowser/utils';
import { metadataClient } from '@/graphql';
import { RainbowError, logger } from '@/logger';
import { createQueryKey } from '@/react-query';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import React, { createContext, useContext } from 'react';
import { createQueryStore, time } from '@/state/internal/createQueryStore';

export type Dapp = {
name: string;
shortName: string;
description: string;
url: string;
urlDisplay: string;
iconUrl: string;
status: string;
trending: boolean;
colors: {
primary: string;
fallback?: string | null;
primary: string;
shadow?: string | null;
};
report: { url: string };
iconUrl: string;
isDirect?: boolean;
name: string;
report: { url: string };
search: {
normalizedName: string;
normalizedNameTokens: string[];
normalizedUrlTokens: string[];
};
shortName: string;
status: string;
trending: boolean;
url: string;
urlDisplay: string;
};

const QUERY_KEY = createQueryKey('dApps', {}, { persisterVersion: 2 });
type DappsState = {
dapps: Dapp[];
findDappByHostname: (hostname: string) => Dapp | undefined;
};

export function useDapps(config?: UseQueryOptions<Dapp[]>): { dapps: Dapp[] } {
const query = useQuery<Dapp[]>(
QUERY_KEY,
async () => {
try {
const response = await metadataClient.getdApps();
if (!response || !response.dApps) {
return [];
}
export const useBrowserDappsStore = createQueryStore<Dapp[], never, DappsState>(
{
fetcher: fetchDapps,
setData: ({ data, set }) => set({ dapps: data }),
cacheTime: time.weeks(1),
staleTime: time.minutes(30),
},

return response.dApps
.filter(dapp => dapp && dapp.status !== 'SCAM')
.map(dapp => ({
name: dapp!.name,
shortName: dapp!.shortName,
description: '', // Remove to cut down on total object size
trending: dapp!.trending || false,
url: dapp!.url,
urlDisplay: formatUrl(dapp!.url),
iconUrl: dapp!.iconURL,
status: dapp!.status,
colors: { primary: dapp!.colors.primary, fallback: dapp!.colors.fallback, shadow: dapp!.colors.shadow },
report: { url: dapp!.report.url },
search: {
normalizedName: dapp!.name.toLowerCase().split(' ').filter(Boolean).join(' '),
normalizedNameTokens: dapp!.name.toLowerCase().split(' ').filter(Boolean),
normalizedUrlTokens: dapp!.url
.toLowerCase()
.replace(/(^\w+:|^)\/\//, '') // Remove protocol from URL
.split(/\/|\?|&|=|\./) // Split the URL into tokens
.filter(Boolean),
},
}));
} catch (e: any) {
logger.error(new RainbowError('[dapps]: Failed to fetch dApps'), { message: e.message });
return [];
}
},
{
staleTime: 1000 * 60 * 20, // 20 minutes
cacheTime: 1000 * 60 * 60 * 24 * 2, // 2 days
retry: 3,
keepPreviousData: true,
...config,
}
);
(_, get) => ({
dapps: [],
findDappByHostname: (hostname: string) => get().dapps.find(dapp => dapp.urlDisplay === hostname),
}),

return { dapps: query.data ?? [] };
}
{ storageKey: 'browserDapps' }
);

interface DappsContextType {
dapps: Dapp[];
}
async function fetchDapps(): Promise<Dapp[]> {
try {
const response = await metadataClient.getdApps();

const DappsContext = createContext<DappsContextType | undefined>(undefined);
if (!response || !response.dApps) return [];

export const useDappsContext = () => {
const context = useContext(DappsContext);
if (!context) {
throw new Error('useDappsContext must be used within a DappsContextProvider');
return response.dApps
.filter(dapp => dapp && dapp.status !== 'SCAM')
.map(dapp =>
dapp
? {
colors: { primary: dapp.colors.primary, fallback: dapp.colors.fallback, shadow: dapp.colors.shadow },
iconUrl: dapp.iconURL,
name: dapp.name,
report: { url: dapp.report.url },
search: {
normalizedName: dapp.name.toLowerCase().split(' ').filter(Boolean).join(' '),
normalizedNameTokens: dapp.name.toLowerCase().split(' ').filter(Boolean),
normalizedUrlTokens: dapp.url
.toLowerCase()
.replace(/(^\w+:|^)\/\//, '') // Remove protocol from URL
.split(/\/|\?|&|=|\./) // Split the URL into tokens
.filter(Boolean),
},
shortName: dapp.shortName,
status: dapp.status,
trending: dapp.trending || false,
url: dapp.url,
urlDisplay: formatUrl(dapp.url),
}
: ({} as Dapp)
);
} catch (e: unknown) {
logger.error(new RainbowError('[dapps]: Failed to fetch dApps'), { message: e instanceof Error ? e.message : 'Unknown error' });
return [];
}
return context;
};

export const DappsContextProvider = ({ children }: { children: React.ReactNode }) => {
const { dapps } = useDapps();
return <DappsContext.Provider value={{ dapps }}>{children}</DappsContext.Provider>;
};
}