Commit 139721ee authored by tom goriunov's avatar tom goriunov Committed by GitHub

migrate to ReactQuery v5 (#1321)

* change signature of hooks

* isLoading -> isPending

* default error type

* dev tools props
parent 3846c3bd
......@@ -20,6 +20,7 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
'plugin:jest/recommended',
'plugin:playwright/playwright-test',
'plugin:@tanstack/eslint-plugin-query/recommended',
],
plugins: [
'es5',
......@@ -31,6 +32,7 @@ module.exports = {
'eslint-plugin-import-helpers',
'jest',
'eslint-plugin-no-cyrillic-string',
'@tanstack/query',
],
parser: '@typescript-eslint/parser',
parserOptions: {
......
......@@ -23,12 +23,15 @@ export default function useApiQuery<R extends ResourceName, E = unknown>(
) {
const apiFetch = useApiFetch();
return useQuery<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>(
getResourceKey(resource, { pathParams, queryParams }),
async() => {
return useQuery<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: getResourceKey(resource, { pathParams, queryParams }),
queryFn: async() => {
// all errors and error typing is handled by react-query
// so error response will never go to the data
// that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>"
return apiFetch(resource, { pathParams, queryParams, fetchParams }) as Promise<ResourcePayload<R>>;
}, queryOptions);
},
...queryOptions,
});
}
......@@ -18,7 +18,7 @@ export default function useQueryClientConfig() {
}
return failureCount < 2;
},
useErrorBoundary: (error) => {
throwOnError: (error) => {
const status = getErrorObjStatusCode(error);
// don't catch error for "Too many requests" response
return status === 429;
......
......@@ -10,27 +10,29 @@ import useFetch from 'lib/hooks/useFetch';
export default function useGetCsrfToken() {
const nodeApiFetch = useFetch();
useQuery(getResourceKey('csrf'), async() => {
if (!isNeedProxy()) {
const url = buildUrl('csrf');
const apiResponse = await fetch(url, { credentials: 'include' });
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
useQuery({
queryKey: getResourceKey('csrf'),
queryFn: async() => {
if (!isNeedProxy()) {
const url = buildUrl('csrf');
const apiResponse = await fetch(url, { credentials: 'include' });
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
if (!csrfFromHeader) {
Sentry.captureException(new Error('Client fetch failed'), { tags: {
source: 'fetch',
'source.resource': 'csrf',
'status.code': 500,
'status.text': 'Unable to obtain csrf token from header',
} });
return;
}
if (!csrfFromHeader) {
Sentry.captureException(new Error('Client fetch failed'), { tags: {
source: 'fetch',
'source.resource': 'csrf',
'status.code': 500,
'status.text': 'Unable to obtain csrf token from header',
} });
return;
}
return { token: csrfFromHeader };
}
return { token: csrfFromHeader };
}
return nodeApiFetch('/node-api/csrf');
}, {
return nodeApiFetch('/node-api/csrf');
},
enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
});
}
......@@ -8,20 +8,18 @@ const feature = config.features.safe;
export default function useIsSafeAddress(hash: string | undefined): boolean {
const fetch = useFetch();
const { data } = useQuery(
[ 'safe_transaction_api', hash ],
async() => {
const { data } = useQuery({
queryKey: [ 'safe_transaction_api', hash ],
queryFn: async() => {
if (!feature.isEnabled || !hash) {
return Promise.reject();
}
return fetch(`${ feature.apiUrl }/${ hash }`, undefined, { omitSentryErrorLog: true });
},
{
enabled: feature.isEnabled && Boolean(hash),
refetchOnMount: false,
},
);
enabled: feature.isEnabled && Boolean(hash),
refetchOnMount: false,
});
return Boolean(data);
}
......@@ -59,7 +59,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
{ getLayout(<Component { ...pageProps }/>) }
</SocketProvider>
</ScrollDirectionProvider>
<ReactQueryDevtools/>
<ReactQueryDevtools buttonPosition="bottom-left" position="left"/>
<GoogleAnalytics/>
</QueryClientProvider>
</AppContextProvider>
......
......@@ -10,7 +10,7 @@ interface Props {
}
const AddressCoinBalanceChart = ({ addressHash }: Props) => {
const { data, isLoading, isError } = useApiQuery('address_coin_balance_chart', {
const { data, isPending, isError } = useApiQuery('address_coin_balance_chart', {
pathParams: { hash: addressHash },
});
......@@ -24,7 +24,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
isError={ isError }
title="Balances"
items={ items }
isLoading={ isLoading }
isLoading={ isPending }
h="300px"
units={ config.chain.currency.symbol }
/>
......
......@@ -6,6 +6,7 @@ import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import type { PaginationParams } from 'ui/shared/pagination/types';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination';
......@@ -15,7 +16,7 @@ import AddressCoinBalanceListItem from './AddressCoinBalanceListItem';
import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem';
interface Props {
query: UseQueryResult<AddressCoinBalanceHistoryResponse> & {
query: UseQueryResult<AddressCoinBalanceHistoryResponse, ResourceError<unknown>> & {
pagination: PaginationParams;
};
}
......
......@@ -27,7 +27,7 @@ const ContractRead = ({ addressHash, isProxy, isCustomAbi }: Props) => {
const apiFetch = useApiFetch();
const account = useWatchAccount();
const { data, isLoading, isError } = useApiQuery(isProxy ? 'contract_methods_read_proxy' : 'contract_methods_read', {
const { data, isPending, isError } = useApiQuery(isProxy ? 'contract_methods_read_proxy' : 'contract_methods_read', {
pathParams: { hash: addressHash },
queryParams: {
is_custom_abi: isCustomAbi ? 'true' : 'false',
......@@ -83,7 +83,7 @@ const ContractRead = ({ addressHash, isProxy, isCustomAbi }: Props) => {
return <DataFetchAlert/>;
}
if (isLoading) {
if (isPending) {
return <ContentLoader/>;
}
......
......@@ -29,7 +29,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
const { chain } = useNetwork();
const { switchNetworkAsync } = useSwitchNetwork();
const { data, isLoading, isError } = useApiQuery(isProxy ? 'contract_methods_write_proxy' : 'contract_methods_write', {
const { data, isPending, isError } = useApiQuery(isProxy ? 'contract_methods_write_proxy' : 'contract_methods_write', {
pathParams: { hash: addressHash },
queryParams: {
is_custom_abi: isCustomAbi ? 'true' : 'false',
......@@ -99,7 +99,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
return <DataFetchAlert/>;
}
if (isLoading) {
if (isPending) {
return <ContentLoader/>;
}
......
......@@ -7,11 +7,12 @@ import type { AddressCounters } from 'types/api/address';
import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
prop: keyof AddressCounters;
query: UseQueryResult<AddressCounters>;
query: UseQueryResult<AddressCounters, ResourceError<unknown>>;
address: string;
onClick: () => void;
isAddressQueryLoading: boolean;
......
......@@ -35,7 +35,7 @@ const TokenSelect = ({ onClick }: Props) => {
const addressQueryData = queryClient.getQueryData<Address>(addressResourceKey);
const { data, isError, isLoading, refetch } = useFetchTokens({ hash: addressQueryData?.hash });
const { data, isError, isPending, refetch } = useFetchTokens({ hash: addressQueryData?.hash });
const tokensResourceKey = getResourceKey('address_tokens', { pathParams: { hash: addressQueryData?.hash }, queryParams: { type: 'ERC-20' } });
const tokensIsFetching = useIsFetching({ queryKey: tokensResourceKey });
......@@ -72,7 +72,7 @@ const TokenSelect = ({ onClick }: Props) => {
handler: handleTokenBalanceMessage,
});
if (isLoading) {
if (isPending) {
return (
<Flex columnGap={ 3 }>
<Skeleton h={ 8 } w="150px" borderRadius="base"/>
......
......@@ -49,12 +49,12 @@ const TokenBalances = () => {
<TokenBalancesItem
name="Net Worth"
value={ addressData?.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) } USD` : 'N/A' }
isLoading={ addressQuery.isLoading || tokenQuery.isLoading }
isLoading={ addressQuery.isPending || tokenQuery.isPending }
/>
<TokenBalancesItem
name={ `${ config.chain.currency.symbol } Balance` }
value={ (!nativeUsd.eq(ZERO) ? `$${ nativeUsd.toFormat(2) } USD | ` : '') + `${ nativeValue } ${ config.chain.currency.symbol }` }
isLoading={ addressQuery.isLoading || tokenQuery.isLoading }
isLoading={ addressQuery.isPending || tokenQuery.isPending }
/>
<TokenBalancesItem
name="Tokens"
......@@ -62,7 +62,7 @@ const TokenBalances = () => {
`${ prefix }$${ tokensInfo.usd.toFormat(2) } USD ` +
tokensNumText
}
isLoading={ addressQuery.isLoading || tokenQuery.isLoading }
isLoading={ addressQuery.isPending || tokenQuery.isPending }
/>
</Flex>
);
......
......@@ -49,7 +49,7 @@ export default function useFetchTokens({ hash }: Props) {
}, [ erc1155query.data, erc20query.data, erc721query.data ]);
return {
isLoading: erc20query.isLoading || erc721query.isLoading || erc1155query.isLoading,
isPending: erc20query.isPending || erc721query.isPending || erc1155query.isPending,
isError: erc20query.isError || erc721query.isError || erc1155query.isError,
data,
refetch,
......
......@@ -57,7 +57,8 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
});
};
const mutation = useMutation(updateApiKey, {
const mutation = useMutation({
mutationFn: updateApiKey,
onSuccess: async(data) => {
const response = data as unknown as ApiKey;
......@@ -148,7 +149,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
size="lg"
type="submit"
isDisabled={ !isDirty }
isLoading={ mutation.isLoading }
isLoading={ mutation.isPending }
>
{ data ? 'Save' : 'Generate API key' }
</Button>
......
......@@ -21,7 +21,7 @@ const hooksConfig = {
test('regular block +@mobile +@dark-mode', async({ mount, page }) => {
const query = {
data: blockMock.base,
isLoading: false,
isPending: false,
} as UseQueryResult<Block, ResourceError>;
const component = await mount(
......@@ -39,7 +39,7 @@ test('regular block +@mobile +@dark-mode', async({ mount, page }) => {
test('genesis block', async({ mount, page }) => {
const query = {
data: blockMock.genesis,
isLoading: false,
isPending: false,
} as UseQueryResult<Block, ResourceError>;
const component = await mount(
......@@ -62,7 +62,7 @@ const customFieldsTest = test.extend({
customFieldsTest('rootstock custom fields', async({ mount, page }) => {
const query = {
data: blockMock.rootstock,
isLoading: false,
isPending: false,
} as UseQueryResult<Block, ResourceError>;
const component = await mount(
......
......@@ -63,7 +63,8 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const formBackgroundColor = useColorModeValue('white', 'gray.900');
const mutation = useMutation(customAbiKey, {
const mutation = useMutation({
mutationFn: customAbiKey,
onSuccess: (data) => {
const response = data as unknown as CustomAbi;
queryClient.setQueryData([ resourceKey('custom_abi') ], (prevData: CustomAbis | undefined) => {
......@@ -175,7 +176,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
size="lg"
type="submit"
isDisabled={ !isDirty }
isLoading={ mutation.isLoading }
isLoading={ mutation.isPending }
>
{ data ? 'Save' : 'Create custom ABI' }
</Button>
......
......@@ -11,10 +11,10 @@ import ChainIndicatorChart from './ChainIndicatorChart';
type Props = UseQueryResult<TimeChartData>;
const ChainIndicatorChartContainer = ({ data, isError, isLoading }: Props) => {
const ChainIndicatorChartContainer = ({ data, isError, isPending }: Props) => {
const content = (() => {
if (isLoading) {
if (isPending) {
return <ContentLoader mt="auto"/>;
}
......
......@@ -5,6 +5,7 @@ import React from 'react';
import type { HomeStats } from 'types/api/stats';
import type { ChainIndicatorId } from 'types/homepage';
import type { ResourceError } from 'lib/api/resources';
import useIsMobile from 'lib/hooks/useIsMobile';
interface Props {
......@@ -14,7 +15,7 @@ interface Props {
icon: React.ReactNode;
isSelected: boolean;
onClick: (id: ChainIndicatorId) => void;
stats: UseQueryResult<HomeStats>;
stats: UseQueryResult<HomeStats, ResourceError<unknown>>;
}
const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats }: Props) => {
......@@ -33,7 +34,7 @@ const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats
return null;
}
if (stats.isLoading) {
if (stats.isPending) {
return (
<Skeleton
h={ 3 }
......
......@@ -41,7 +41,7 @@ const ChainIndicators = () => {
}
const valueTitle = (() => {
if (statsQueryResult.isLoading) {
if (statsQueryResult.isPending) {
return <Skeleton h="48px" w="215px" mt={ 3 } mb={ 4 }/>;
}
......
......@@ -24,15 +24,14 @@ function isAppCategoryMatches(category: string, app: MarketplaceAppOverview, fav
export default function useMarketplaceApps(filter: string, selectedCategoryId: string = MarketplaceCategory.ALL, favoriteApps: Array<string> = []) {
const apiFetch = useApiFetch();
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>(
[ 'marketplace-apps' ],
async() => apiFetch(configUrl, undefined, { resource: 'marketplace-apps' }),
{
select: (data) => (data as Array<MarketplaceAppOverview>).sort((a, b) => a.title.localeCompare(b.title)),
placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined,
staleTime: Infinity,
enabled: feature.isEnabled,
});
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>({
queryKey: [ 'marketplace-apps' ],
queryFn: async() => apiFetch(configUrl, undefined, { resource: 'marketplace-apps' }),
select: (data) => (data as Array<MarketplaceAppOverview>).sort((a, b) => a.title.localeCompare(b.title)),
placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined,
staleTime: Infinity,
enabled: feature.isEnabled,
});
const displayedApps = React.useMemo(() => {
return data?.filter(app => isAppNameMatches(filter, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps)) || [];
......
......@@ -64,7 +64,7 @@ const ContractVerification = () => {
return <DataFetchAlert/>;
}
if (configQuery.isLoading || contractQuery.isLoading || isVerifiedContract) {
if (configQuery.isPending || contractQuery.isPending || isVerifiedContract) {
return <ContentLoader/>;
}
......
......@@ -108,7 +108,7 @@ const CsvExport = () => {
return <DataFetchAlert/>;
}
if (addressQuery.isLoading) {
if (addressQuery.isPending) {
return <ContentLoader/>;
}
......
......@@ -34,9 +34,9 @@ const MarketplaceApp = () => {
const router = useRouter();
const id = getQueryParamString(router.query.id);
const { isLoading, isError, error, data } = useQuery<unknown, ResourceError<unknown>, MarketplaceAppOverview>(
[ 'marketplace-apps', id ],
async() => {
const { isPending, isError, error, data } = useQuery<unknown, ResourceError<unknown>, MarketplaceAppOverview>({
queryKey: [ 'marketplace-apps', id ],
queryFn: async() => {
const result = await apiFetch<Array<MarketplaceAppOverview>, unknown>(configUrl, undefined, { resource: 'marketplace-apps' });
if (!Array.isArray(result)) {
throw result;
......@@ -49,12 +49,10 @@ const MarketplaceApp = () => {
return item;
},
{
enabled: feature.isEnabled,
},
);
enabled: feature.isEnabled,
});
const [ isFrameLoading, setIsFrameLoading ] = useState(isLoading);
const [ isFrameLoading, setIsFrameLoading ] = useState(isPending);
const { colorMode } = useColorMode();
const handleIframeLoad = useCallback(() => {
......@@ -106,7 +104,7 @@ const MarketplaceApp = () => {
return (
<>
{ !isLoading && <PageTitle title={ data.title } backLink={ backLink }/> }
{ !isPending && <PageTitle title={ data.title } backLink={ backLink }/> }
<Center
h="100vh"
mx={{ base: -4, lg: -12 }}
......
......@@ -9,11 +9,11 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import UserAvatar from 'ui/shared/UserAvatar';
const MyProfile = () => {
const { data, isLoading, isError } = useFetchProfileInfo();
const { data, isPending, isError } = useFetchProfileInfo();
useRedirectForInvalidAuthToken();
const content = (() => {
if (isLoading) {
if (isPending) {
return <ContentLoader/>;
}
......
......@@ -54,7 +54,7 @@ const SearchResultsPageContent = () => {
}
}
!redirectCheckQuery.isLoading && setShowContent(true);
!redirectCheckQuery.isPending && setShowContent(true);
}, [ redirectCheckQuery, router, debouncedSearchTerm, showContent ]);
const handleSubmit = React.useCallback((event: FormEvent<HTMLFormElement>) => {
......
......@@ -42,7 +42,7 @@ const WatchList: React.FC = () => {
}, [ addressModalProps ]);
const onAddOrEditSuccess = useCallback(async() => {
await queryClient.refetchQueries([ resourceKey('watchlist') ]);
await queryClient.refetchQueries({ queryKey: [ resourceKey('watchlist') ] });
setAddressModalData(undefined);
addressModalProps.onClose();
}, [ addressModalProps, queryClient ]);
......
......@@ -44,22 +44,23 @@ const AddressForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisibl
const formBackgroundColor = useColorModeValue('white', 'gray.900');
const { mutate } = useMutation((formData: Inputs) => {
const body = {
name: formData?.tag,
address_hash: formData?.address,
};
const { mutate } = useMutation({
mutationFn: (formData: Inputs) => {
const body = {
name: formData?.tag,
address_hash: formData?.address,
};
const isEdit = data?.id;
if (isEdit) {
return apiFetch('private_tags_address', {
pathParams: { id: data.id },
fetchParams: { method: 'PUT', body },
});
}
const isEdit = data?.id;
if (isEdit) {
return apiFetch('private_tags_address', {
pathParams: { id: data.id },
fetchParams: { method: 'PUT', body },
});
}
return apiFetch('private_tags_address', { fetchParams: { method: 'POST', body } });
}, {
return apiFetch('private_tags_address', { fetchParams: { method: 'POST', body } });
},
onError: (error: ResourceErrorAccount<AddressTagErrors>) => {
setPending(false);
const errorMap = error.payload?.errors;
......
......@@ -47,22 +47,23 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVi
const queryClient = useQueryClient();
const apiFetch = useApiFetch();
const { mutate } = useMutation((formData: Inputs) => {
const body = {
name: formData?.tag,
transaction_hash: formData?.transaction,
};
const isEdit = data?.id;
if (isEdit) {
return apiFetch('private_tags_tx', {
pathParams: { id: data.id },
fetchParams: { method: 'PUT', body },
});
}
return apiFetch('private_tags_tx', { fetchParams: { method: 'POST', body } });
}, {
const { mutate } = useMutation({
mutationFn: (formData: Inputs) => {
const body = {
name: formData?.tag,
transaction_hash: formData?.transaction,
};
const isEdit = data?.id;
if (isEdit) {
return apiFetch('private_tags_tx', {
pathParams: { id: data.id },
fetchParams: { method: 'PUT', body },
});
}
return apiFetch('private_tags_tx', { fetchParams: { method: 'POST', body } });
},
onError: (error: ResourceErrorAccount<TransactionTagErrors>) => {
setPending(false);
const errorMap = error.payload?.errors;
......@@ -76,7 +77,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVi
}
},
onSuccess: async() => {
await queryClient.refetchQueries([ resourceKey('private_tags_tx') ]);
await queryClient.refetchQueries({ queryKey: [ resourceKey('private_tags_tx') ] });
await onSuccess();
onClose();
setPending(false);
......
......@@ -109,7 +109,8 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
});
};
const mutation = useMutation(updatePublicTag, {
const mutation = useMutation({
mutationFn: updatePublicTag,
onSuccess: async(data) => {
const response = data as unknown as PublicTag;
......@@ -237,7 +238,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
size="lg"
type="submit"
isDisabled={ !isDirty }
isLoading={ mutation.isLoading }
isLoading={ mutation.isPending }
>
Send request
</Button>
......
......@@ -39,7 +39,8 @@ const DeleteModal: React.FC<Props> = ({
onClose();
}, [ onClose, setAlertVisible ]);
const mutation = useMutation(mutationFn, {
const mutation = useMutation({
mutationFn,
onSuccess: async() => {
onSuccess();
onClose();
......@@ -70,7 +71,7 @@ const DeleteModal: React.FC<Props> = ({
<Button
size="lg"
onClick={ onDeleteClick }
isLoading={ mutation.isLoading }
isLoading={ mutation.isPending }
// FIXME: chackra's button is disabled when isLoading
isDisabled={ false }
>
......
......@@ -13,9 +13,9 @@ export default function useNftMediaType(url: string | null, isEnabled: boolean)
const fetch = useFetch();
const { data } = useQuery<unknown, ResourceError<unknown>, MediaType>(
[ 'nft-media-type', url ],
async() => {
const { data } = useQuery<unknown, ResourceError<unknown>, MediaType>({
queryKey: [ 'nft-media-type', url ],
queryFn: async() => {
if (!url) {
return 'image';
}
......@@ -41,10 +41,9 @@ export default function useNftMediaType(url: string | null, isEnabled: boolean)
return 'image';
}
},
{
enabled: isEnabled && Boolean(url),
staleTime: Infinity,
});
enabled: isEnabled && Boolean(url),
staleTime: Infinity,
});
return data;
}
......@@ -96,13 +96,12 @@ const Footer = () => {
const fetch = useFetch();
const { isLoading, data: linksData } = useQuery<unknown, ResourceError<unknown>, Array<CustomLinksGroup>>(
[ 'footer-links' ],
async() => fetch(config.UI.footer.links || '', undefined, { resource: 'footer-links' }),
{
enabled: Boolean(config.UI.footer.links),
staleTime: Infinity,
});
const { isPending, data: linksData } = useQuery<unknown, ResourceError<unknown>, Array<CustomLinksGroup>>({
queryKey: [ 'footer-links' ],
queryFn: async() => fetch(config.UI.footer.links || '', undefined, { resource: 'footer-links' }),
enabled: Boolean(config.UI.footer.links),
staleTime: Infinity,
});
return (
<Flex
......@@ -170,7 +169,7 @@ const Footer = () => {
{ BLOCKSCOUT_LINKS.map(link => <FooterLinkItem { ...link } key={ link.text }/>) }
</Grid>
</Box>
{ config.UI.footer.links && isLoading && (
{ config.UI.footer.links && isPending && (
Array.from(Array(3)).map((i, index) => (
<Box minW="160px" key={ index }>
<Skeleton w="120px" h="20px" mb={ 6 }/>
......
......@@ -13,7 +13,7 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
const IntTxsIndexingStatus = () => {
const { data, isError, isLoading } = useApiQuery('homepage_indexing_status');
const { data, isError, isPending } = useApiQuery('homepage_indexing_status');
const bgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
const hintTextcolor = useColorModeValue('black', 'white');
......@@ -42,7 +42,7 @@ const IntTxsIndexingStatus = () => {
handler: handleInternalTxsIndexStatus,
});
if (isError || isLoading) {
if (isError || isPending) {
return null;
}
......
......@@ -18,17 +18,17 @@ const IndexingBlocksAlert = () => {
const cookiesString = appProps.cookies;
const [ hasAlertCookie ] = React.useState(cookies.get(cookies.NAMES.INDEXING_ALERT, cookiesString) === 'true');
const { data, isError, isLoading } = useApiQuery('homepage_indexing_status', {
const { data, isError, isPending } = useApiQuery('homepage_indexing_status', {
queryOptions: {
enabled: !config.UI.indexingAlert.blocks.isHidden,
},
});
React.useEffect(() => {
if (!isLoading && !isError) {
if (!isPending && !isError) {
cookies.set(cookies.NAMES.INDEXING_ALERT, data.finished_indexing_blocks ? 'false' : 'true');
}
}, [ data, isError, isLoading ]);
}, [ data, isError, isPending ]);
const queryClient = useQueryClient();
......@@ -62,7 +62,7 @@ const IndexingBlocksAlert = () => {
return null;
}
if (isLoading) {
if (isPending) {
return hasAlertCookie ? <Skeleton h={{ base: '96px', lg: '48px' }} w="100%"/> : null;
}
......
......@@ -13,21 +13,20 @@ export default function useNetworkMenu() {
const { isOpen, onClose, onOpen, onToggle } = useDisclosure();
const apiFetch = useApiFetch();
const { isLoading, data } = useQuery<unknown, ResourceError<unknown>, Array<FeaturedNetwork>>(
[ 'featured-network' ],
async() => apiFetch(config.UI.sidebar.featuredNetworks || '', undefined, { resource: 'featured-network' }),
{
enabled: Boolean(config.UI.sidebar.featuredNetworks) && isOpen,
staleTime: Infinity,
});
const { isPending, data } = useQuery<unknown, ResourceError<unknown>, Array<FeaturedNetwork>>({
queryKey: [ 'featured-network' ],
queryFn: async() => apiFetch(config.UI.sidebar.featuredNetworks || '', undefined, { resource: 'featured-network' }),
enabled: Boolean(config.UI.sidebar.featuredNetworks) && isOpen,
staleTime: Infinity,
});
return React.useMemo(() => ({
isOpen,
onClose,
onOpen,
onToggle,
isLoading,
isPending,
data,
availableTabs: NETWORK_GROUPS.filter((tab) => data?.some(({ group }) => group === tab)),
}), [ isOpen, onClose, onOpen, onToggle, data, isLoading ]);
}), [ isOpen, onClose, onOpen, onToggle, data, isPending ]);
}
......@@ -9,15 +9,15 @@ import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuDesktop = () => {
const { data, error, isLoading } = useFetchProfileInfo();
const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl();
const [ hasMenu, setHasMenu ] = React.useState(false);
React.useEffect(() => {
if (!isLoading) {
if (!isPending) {
setHasMenu(Boolean(data));
}
}, [ data, error?.status, isLoading ]);
}, [ data, error?.status, isPending ]);
const handleSignInClick = React.useCallback(() => {
mixpanel.logEvent(
......
......@@ -11,7 +11,7 @@ import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { data, error, isLoading } = useFetchProfileInfo();
const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl();
const [ hasMenu, setHasMenu ] = React.useState(false);
......@@ -24,10 +24,10 @@ const ProfileMenuMobile = () => {
}, []);
React.useEffect(() => {
if (!isLoading) {
if (!isPending) {
setHasMenu(Boolean(data));
}
}, [ data, error?.status, isLoading ]);
}, [ data, error?.status, isPending ]);
const buttonProps: Partial<ButtonProps> = (() => {
if (hasMenu || !loginUrl) {
......
......@@ -103,7 +103,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
const bgColor = useColorModeValue('white', 'gray.900');
const content = (() => {
if (query.isLoading || marketplaceApps.isPlaceholderData) {
if (query.isPending || marketplaceApps.isPlaceholderData) {
return <ContentLoader text="We are searching, please wait... " fontSize="sm"/>;
}
......
......@@ -71,7 +71,7 @@ const Sol2UmlDiagram = ({ addressHash }: Props) => {
throw Error('Uml diagram fetch error', { cause: contractQuery.error as unknown as Error });
}
if (contractQuery.isLoading || umlQuery.isLoading) {
if (contractQuery.isPending || umlQuery.isPending) {
return <ContentLoader/>;
}
......
......@@ -27,7 +27,7 @@ const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const { data, isLoading, isError } = useApiQuery('stats_line', {
const { data, isPending, isError } = useApiQuery('stats_line', {
pathParams: { id },
queryParams: {
from: startDate,
......@@ -56,7 +56,7 @@ const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError
title={ title }
units={ units }
description={ description }
isLoading={ isLoading }
isLoading={ isPending }
minH="230px"
/>
);
......
......@@ -7,6 +7,7 @@ import { scroller } from 'react-scroll';
import type { TokenInfo } from 'types/api/token';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import getCurrencyValue from 'lib/getCurrencyValue';
import { TOKEN_COUNTERS } from 'stubs/token';
......@@ -18,7 +19,7 @@ import TruncatedValue from 'ui/shared/TruncatedValue';
import TokenNftMarketplaces from './TokenNftMarketplaces';
interface Props {
tokenQuery: UseQueryResult<TokenInfo>;
tokenQuery: UseQueryResult<TokenInfo, ResourceError<unknown>>;
}
const TokenDetails = ({ tokenQuery }: Props) => {
......
......@@ -5,24 +5,25 @@ import React from 'react';
import type { TokenVerifiedInfo as TTokenVerifiedInfo } from 'types/api/token';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import LinkExternal from 'ui/shared/LinkExternal';
import TokenProjectInfo from './TokenProjectInfo';
interface Props {
verifiedInfoQuery: UseQueryResult<TTokenVerifiedInfo>;
verifiedInfoQuery: UseQueryResult<TTokenVerifiedInfo, ResourceError<unknown>>;
}
const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => {
const { data, isLoading, isError } = verifiedInfoQuery;
const { data, isPending, isError } = verifiedInfoQuery;
const content = (() => {
if (!config.features.verifiedTokens.isEnabled) {
return null;
}
if (isLoading) {
if (isPending) {
return (
<>
<Skeleton w="100px" h="30px" borderRadius="base"/>
......
......@@ -112,7 +112,7 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
return <DataFetchAlert/>;
}
if (configQuery.isLoading) {
if (configQuery.isPending) {
return <ContentLoader/>;
}
......
......@@ -24,7 +24,7 @@ const TxLogs = () => {
},
});
if (!txInfo.isLoading && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
if (!txInfo.isPending && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
}
......
......@@ -49,7 +49,7 @@ const TxRawTrace = () => {
handler: handleRawTraceMessage,
});
if (!txInfo.isLoading && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
if (!txInfo.isPending && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
}
......
......@@ -31,7 +31,7 @@ const TxState = () => {
},
});
if (!txInfo.isLoading && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
if (!txInfo.isPending && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
}
......
......@@ -45,7 +45,7 @@ const TxTokenTransfer = () => {
setTypeFilter(nextValue);
}, [ tokenTransferQuery ]);
if (!txsInfo.isLoading && !txsInfo.isPlaceholderData && !txsInfo.isError && !txsInfo.data.status) {
if (!txsInfo.isPending && !txsInfo.isPlaceholderData && !txsInfo.isError && !txsInfo.data.status) {
return txsInfo.socketStatus ? <TxSocketAlert status={ txsInfo.socketStatus }/> : <TxPendingAlert/>;
}
......
......@@ -38,7 +38,7 @@ export default function useFetchTxInfo({ onTxStatusUpdate, updateDelay }: Params
placeholderData: config.features.zkEvmRollup.isEnabled ? TX_ZKEVM_L2 : TX,
},
});
const { data, isError, isLoading } = queryResult;
const { data, isError, isPending } = queryResult;
const handleStatusUpdateMessage: SocketMessage.TxStatusUpdate['handler'] = React.useCallback(async() => {
updateDelay && await delay(updateDelay);
......@@ -60,7 +60,7 @@ export default function useFetchTxInfo({ onTxStatusUpdate, updateDelay }: Params
topic: `transactions:${ hash }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: isLoading || isError || data.status !== null,
isDisabled: isPending || isError || data.status !== null,
});
useSocketMessage({
channel,
......
......@@ -11,14 +11,14 @@ interface Props {
}
const TxAdditionalInfoContainer = ({ hash }: Props) => {
const { data, isError, isLoading } = useApiQuery('tx', {
const { data, isError, isPending } = useApiQuery('tx', {
pathParams: { hash },
queryOptions: {
refetchOnMount: false,
},
});
if (isLoading) {
if (isPending) {
return (
<Box>
<Skeleton w="130px" h="24px" borderRadius="full" mb={ 6 }/>
......
......@@ -4,17 +4,18 @@ import React from 'react';
import type { TxsResponse } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import type { ResourceError } from 'lib/api/resources';
import * as cookies from 'lib/cookies';
import sortTxs from 'lib/tx/sortTxs';
type HookResult = UseQueryResult<TxsResponse> & {
type HookResult = UseQueryResult<TxsResponse, ResourceError<unknown>> & {
sorting: Sort;
setSortByField: (field: 'val' | 'fee') => () => void;
setSortByValue: (value: Sort | undefined) => void;
}
export default function useTxsSort(
queryResult: UseQueryResult<TxsResponse>,
queryResult: UseQueryResult<TxsResponse, ResourceError<unknown>>,
): HookResult {
const [ sorting, setSorting ] = React.useState<Sort>(cookies.get(cookies.NAMES.TXS_SORT) as Sort);
......@@ -61,7 +62,7 @@ export default function useTxsSort(
}, []);
return React.useMemo(() => {
if (queryResult.isError || queryResult.isLoading) {
if (queryResult.isError || queryResult.isPending) {
return { ...queryResult, setSortByField, setSortByValue, sorting };
}
......
......@@ -13,7 +13,7 @@ const VerifiedContractsCounters = () => {
}
const content = (() => {
if (countersQuery.isLoading) {
if (countersQuery.isPending) {
const item = <Skeleton w={{ base: '100%', lg: 'calc((100% - 12px)/2)' }} h="69px" borderRadius="12px"/>;
return (
<>
......
......@@ -106,7 +106,8 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
}
}
const { mutate } = useMutation(updateWatchlist, {
const { mutate } = useMutation({
mutationFn: updateWatchlist,
onSuccess: async() => {
await onSuccess();
setPending(false);
......
......@@ -59,15 +59,16 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick }: Props) =
});
}, [ notificationToast ]);
const { mutate } = useMutation(() => {
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return apiFetch('watchlist', {
pathParams: { id: item.id },
fetchParams: { method: 'PUT', body },
});
}, {
const { mutate } = useMutation({
mutationFn: () => {
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return apiFetch('watchlist', {
pathParams: { id: item.id },
fetchParams: { method: 'PUT', body },
});
},
onError: () => {
showErrorToast();
setNotificationEnabled(prevState => !prevState);
......
......@@ -63,15 +63,16 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick }: Pro
});
}, [ notificationToast ]);
const { mutate } = useMutation(() => {
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return apiFetch('watchlist', {
pathParams: { id: item.id },
fetchParams: { method: 'PUT', body },
});
}, {
const { mutate } = useMutation({
mutationFn: () => {
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return apiFetch('watchlist', {
pathParams: { id: item.id },
fetchParams: { method: 'PUT', body },
});
},
onError: () => {
showErrorToast();
setNotificationEnabled(prevState => !prevState);
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment