Skip to content

Commit 34685ac

Browse files
authored
Create useApiQueries hook to manage multiple API requests to the same resource in parallel (#3136)
1 parent 64dacbc commit 34685ac

File tree

10 files changed

+163
-124
lines changed

10 files changed

+163
-124
lines changed

lib/api/useApiQueries.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { UseQueryOptions } from '@tanstack/react-query';
2+
import { useQueries } from '@tanstack/react-query';
3+
4+
import type { ResourceError, ResourceName, ResourcePayload } from './resources';
5+
import useApiFetch from './useApiFetch';
6+
import type { Params as ApiQueryParams } from './useApiQuery';
7+
import { getResourceKey } from './useApiQuery';
8+
9+
export interface ReturnType<R extends ResourceName, E = unknown> {
10+
isLoading: boolean;
11+
isPlaceholderData: boolean;
12+
isError: boolean;
13+
isSuccess: boolean;
14+
data: Array<ResourcePayload<R>> | undefined;
15+
error: ResourceError<E> | null | undefined;
16+
}
17+
18+
export default function useApiQueries<R extends ResourceName, E = unknown>(
19+
resource: R,
20+
params: Array<ApiQueryParams<R, E>>,
21+
queryOptions: Partial<Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, Array<ResourcePayload<R>>>, 'queries'>>,
22+
) {
23+
const apiFetch = useApiFetch();
24+
25+
return useQueries<Array<UseQueryOptions<ResourcePayload<R>, ResourceError<E>>>, ReturnType<R, E>>({
26+
queries: params.map(({ pathParams, queryParams, queryOptions, fetchParams, logError, chain }) => {
27+
return {
28+
queryKey: getResourceKey(resource, { pathParams, queryParams, chainId: chain?.id }),
29+
queryFn: async({ signal }) => {
30+
return apiFetch(resource, { pathParams, queryParams, logError, chain, fetchParams: { ...fetchParams, signal } }) as Promise<ResourcePayload<R>>;
31+
},
32+
...queryOptions,
33+
};
34+
}),
35+
combine: (results) => {
36+
const isError = results.some((result) => result.isError);
37+
const isSuccess = results.every((result) => result.isSuccess);
38+
39+
return {
40+
isLoading: results.some((result) => result.isLoading),
41+
isPlaceholderData: results.some((result) => result.isPlaceholderData),
42+
isError,
43+
isSuccess,
44+
data: isSuccess ? results.map((result) => result.data).flat() as Array<ResourcePayload<R>> : undefined,
45+
error: isError ? results.find((result) => result.error)?.error : undefined,
46+
} satisfies ReturnType<R, E>;
47+
},
48+
...queryOptions,
49+
});
50+
}

types/api/noves.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,3 @@ export type NovesDescribeTxsResponse = {
118118
type: string;
119119
description: string;
120120
};
121-
122-
export interface NovesTxTranslation {
123-
data?: NovesDescribeTxsResponse;
124-
isLoading: boolean;
125-
}

types/api/transaction.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { BlockTransactionsResponse } from './block';
44
import type { DecodedInput } from './decodedInput';
55
import type { Fee } from './fee';
66
import type { ChainInfo, MessageStatus } from './interop';
7-
import type { NovesTxTranslation } from './noves';
87
import type { OptimisticL2WithdrawalClaimInfo, OptimisticL2WithdrawalStatus } from './optimisticL2';
98
import type { ScrollL2BlockStatus } from './scrollL2';
109
import type { TokenInfo } from './token';
@@ -104,8 +103,6 @@ export type Transaction = {
104103
blob_gas_price?: string;
105104
burnt_blob_fee?: string;
106105
max_fee_per_blob_gas?: string;
107-
// Noves-fi
108-
translation?: NovesTxTranslation;
109106
arbitrum?: ArbitrumTransactionData;
110107
scroll?: ScrollTransactionData;
111108
// EIP-7702

ui/txs/TxTranslationType.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@ import { camelCaseToSentence } from './noves/utils';
88
import TxType from './TxType';
99

1010
export interface Props {
11-
types: Array<TransactionType>;
11+
txTypes: Array<TransactionType>;
1212
isLoading?: boolean;
13-
translatationType: string | undefined;
13+
type: string | undefined;
1414
}
1515

16-
const TxTranslationType = ({ types, isLoading, translatationType }: Props) => {
16+
const FILTERED_TYPES = [ 'unclassified' ];
1717

18-
const filteredTypes = [ 'unclassified' ];
18+
const TxTranslationType = ({ txTypes, isLoading, type }: Props) => {
1919

20-
if (!translatationType || filteredTypes.includes(translatationType)) {
21-
return <TxType types={ types } isLoading={ isLoading }/>;
20+
if (!type || FILTERED_TYPES.includes(type.toLowerCase())) {
21+
return <TxType types={ txTypes } isLoading={ isLoading }/>;
2222
}
2323

2424
return (
2525
<Badge colorPalette="purple" loading={ isLoading }>
26-
{ camelCaseToSentence(translatationType) }
26+
{ camelCaseToSentence(type) }
2727
</Badge>
2828
);
2929

ui/txs/TxsContent.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ const TxsContent = ({
6464
setSorting?.(value);
6565
}, [ sort, setSorting ]);
6666

67-
const itemsWithTranslation = useDescribeTxs(items, currentAddress, isPlaceholderData);
67+
const translationQuery = useDescribeTxs(items, currentAddress, isPlaceholderData);
6868

69-
const content = itemsWithTranslation ? (
69+
const content = items && items.length > 0 ? (
7070
<>
7171
<Box hideFrom="lg">
7272
<TxsList
@@ -75,12 +75,13 @@ const TxsContent = ({
7575
isLoading={ isPlaceholderData }
7676
enableTimeIncrement={ enableTimeIncrement }
7777
currentAddress={ currentAddress }
78-
items={ itemsWithTranslation }
78+
items={ items }
79+
translationQuery={ translationQuery }
7980
/>
8081
</Box>
8182
<Box hideBelow="lg">
8283
<TxsTable
83-
txs={ itemsWithTranslation }
84+
txs={ items }
8485
sort={ sort }
8586
onSortToggle={ setSorting ? onSortToggle : undefined }
8687
showBlockInfo={ showBlockInfo }
@@ -90,6 +91,7 @@ const TxsContent = ({
9091
enableTimeIncrement={ enableTimeIncrement }
9192
isLoading={ isPlaceholderData }
9293
stickyHeader={ stickyHeader }
94+
translationQuery={ translationQuery }
9395
/>
9496
</Box>
9597
</>
@@ -117,7 +119,7 @@ const TxsContent = ({
117119
return (
118120
<DataListDisplay
119121
isError={ isError }
120-
itemsNum={ itemsWithTranslation?.length }
122+
itemsNum={ items?.length }
121123
emptyText="There are no transactions."
122124
actionBar={ actionBar }
123125
filterProps={{

ui/txs/TxsList.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useMultichainContext } from 'lib/contexts/multichain';
88
import useInitialList from 'lib/hooks/useInitialList';
99
import useLazyRenderedList from 'lib/hooks/useLazyRenderedList';
1010

11+
import type { TxsTranslationQuery } from './noves/useDescribeTxs';
1112
import TxsSocketNotice from './socket/TxsSocketNotice';
1213
import TxsListItem from './TxsListItem';
1314

@@ -18,6 +19,7 @@ interface Props {
1819
currentAddress?: string;
1920
isLoading: boolean;
2021
items: Array<Transaction>;
22+
translationQuery?: TxsTranslationQuery;
2123
}
2224

2325
const TxsList = (props: Props) => {
@@ -33,18 +35,22 @@ const TxsList = (props: Props) => {
3335
return (
3436
<Box>
3537
{ props.socketType && <TxsSocketNotice type={ props.socketType } place="list" isLoading={ props.isLoading }/> }
36-
{ props.items.slice(0, renderedItemsNum).map((tx, index) => (
37-
<TxsListItem
38-
key={ tx.hash + (props.isLoading ? index : '') }
39-
tx={ tx }
40-
showBlockInfo={ props.showBlockInfo }
41-
currentAddress={ props.currentAddress }
42-
enableTimeIncrement={ props.enableTimeIncrement }
43-
isLoading={ props.isLoading }
44-
animation={ initialList.getAnimationProp(tx) }
45-
chainData={ chainData }
46-
/>
47-
)) }
38+
{ props.items.slice(0, renderedItemsNum).map((tx, index) => {
39+
return (
40+
<TxsListItem
41+
key={ tx.hash + (props.isLoading ? index : '') }
42+
tx={ tx }
43+
showBlockInfo={ props.showBlockInfo }
44+
currentAddress={ props.currentAddress }
45+
enableTimeIncrement={ props.enableTimeIncrement }
46+
isLoading={ props.isLoading }
47+
animation={ initialList.getAnimationProp(tx) }
48+
chainData={ chainData }
49+
translationIsLoading={ props.translationQuery?.isLoading }
50+
translationData={ props.translationQuery?.data?.find(({ txHash }) => txHash.toLowerCase() === tx.hash.toLowerCase()) }
51+
/>
52+
);
53+
}) }
4854
<Box ref={ cutRef } h={ 0 }/>
4955
</Box>
5056
);

ui/txs/TxsListItem.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from '@chakra-ui/react';
55
import React from 'react';
66

7+
import type { NovesDescribeTxsResponse } from 'types/api/noves';
78
import type { Transaction } from 'types/api/transaction';
89
import type { ClusterChainConfig } from 'types/multichain';
910

@@ -33,20 +34,32 @@ type Props = {
3334
isLoading?: boolean;
3435
animation?: string;
3536
chainData?: ClusterChainConfig;
37+
translationIsLoading?: boolean;
38+
translationData?: NovesDescribeTxsResponse;
3639
};
3740

38-
const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeIncrement, animation, chainData }: Props) => {
41+
const TxsListItem = ({
42+
tx,
43+
isLoading,
44+
showBlockInfo,
45+
currentAddress,
46+
enableTimeIncrement,
47+
animation,
48+
chainData,
49+
translationIsLoading,
50+
translationData,
51+
}: Props) => {
3952
const dataTo = tx.to ? tx.to : tx.created_contract;
4053

4154
return (
4255
<ListItemMobile display="block" width="100%" animation={ animation } key={ tx.hash }>
4356
<Flex justifyContent="space-between" alignItems="flex-start" mt={ 4 }>
4457
<HStack flexWrap="wrap">
45-
{ tx.translation ? (
58+
{ translationIsLoading || translationData ? (
4659
<TxTranslationType
47-
types={ tx.transaction_types }
48-
isLoading={ isLoading || tx.translation.isLoading }
49-
translatationType={ tx.translation.data?.type }
60+
txTypes={ tx.transaction_types }
61+
isLoading={ isLoading || translationIsLoading }
62+
type={ translationData?.type }
5063
/>
5164
) :
5265
<TxType types={ tx.transaction_types } isLoading={ isLoading }/>

ui/txs/TxsTable.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { currencyUnits } from 'lib/units';
1212
import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
1313
import TimeFormatToggle from 'ui/shared/time/TimeFormatToggle';
1414

15+
import type { TxsTranslationQuery } from './noves/useDescribeTxs';
1516
import TxsSocketNotice from './socket/TxsSocketNotice';
1617
import TxsTableItem from './TxsTableItem';
1718

@@ -26,6 +27,7 @@ type Props = {
2627
enableTimeIncrement?: boolean;
2728
isLoading?: boolean;
2829
stickyHeader?: boolean;
30+
translationQuery?: TxsTranslationQuery;
2931
};
3032

3133
const TxsTable = ({
@@ -39,6 +41,7 @@ const TxsTable = ({
3941
enableTimeIncrement,
4042
isLoading,
4143
stickyHeader = true,
44+
translationQuery,
4245
}: Props) => {
4346
const { cutRef, renderedItemsNum } = useLazyRenderedList(txs, !isLoading);
4447
const initialList = useInitialList({
@@ -118,18 +121,22 @@ const TxsTable = ({
118121
</TableHeaderComponent>
119122
<TableBody>
120123
{ socketType && <TxsSocketNotice type={ socketType } place="table" isLoading={ isLoading }/> }
121-
{ txs.slice(0, renderedItemsNum).map((item, index) => (
122-
<TxsTableItem
123-
key={ item.hash + (isLoading ? index : '') }
124-
tx={ item }
125-
showBlockInfo={ showBlockInfo }
126-
currentAddress={ currentAddress }
127-
enableTimeIncrement={ enableTimeIncrement }
128-
isLoading={ isLoading }
129-
animation={ initialList.getAnimationProp(item) }
130-
chainData={ chainData }
131-
/>
132-
)) }
124+
{ txs.slice(0, renderedItemsNum).map((item, index) => {
125+
return (
126+
<TxsTableItem
127+
key={ item.hash + (isLoading ? index : '') }
128+
tx={ item }
129+
showBlockInfo={ showBlockInfo }
130+
currentAddress={ currentAddress }
131+
enableTimeIncrement={ enableTimeIncrement }
132+
isLoading={ isLoading }
133+
animation={ initialList.getAnimationProp(item) }
134+
chainData={ chainData }
135+
translationIsLoading={ translationQuery?.isLoading }
136+
translationData={ translationQuery?.data?.find(({ txHash }) => txHash.toLowerCase() === item.hash.toLowerCase()) }
137+
/>
138+
);
139+
}) }
133140
</TableBody>
134141
</TableRoot>
135142
<div ref={ cutRef }/>

ui/txs/TxsTableItem.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Flex, VStack } from '@chakra-ui/react';
22
import React from 'react';
33

4+
import type { NovesDescribeTxsResponse } from 'types/api/noves';
45
import type { Transaction } from 'types/api/transaction';
56
import type { ClusterChainConfig } from 'types/multichain';
67

@@ -30,9 +31,21 @@ type Props = {
3031
isLoading?: boolean;
3132
animation?: string;
3233
chainData?: ClusterChainConfig;
34+
translationIsLoading?: boolean;
35+
translationData?: NovesDescribeTxsResponse;
3336
};
3437

35-
const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, isLoading, animation, chainData }: Props) => {
38+
const TxsTableItem = ({
39+
tx,
40+
showBlockInfo,
41+
currentAddress,
42+
enableTimeIncrement,
43+
isLoading,
44+
animation,
45+
chainData,
46+
translationIsLoading,
47+
translationData,
48+
}: Props) => {
3649
const dataTo = tx.to ? tx.to : tx.created_contract;
3750

3851
return (
@@ -65,11 +78,11 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
6578
</TableCell>
6679
<TableCell>
6780
<VStack alignItems="start">
68-
{ tx.translation ? (
81+
{ translationIsLoading || translationData ? (
6982
<TxTranslationType
70-
types={ tx.transaction_types }
71-
isLoading={ isLoading || tx.translation.isLoading }
72-
translatationType={ tx.translation.data?.type }
83+
txTypes={ tx.transaction_types }
84+
isLoading={ isLoading || translationIsLoading }
85+
type={ translationData?.type }
7386
/>
7487
) :
7588
<TxType types={ tx.transaction_types } isLoading={ isLoading }/>

0 commit comments

Comments
 (0)