diff --git a/src/components/TransactionDetails.tsx b/src/components/TransactionDetails.tsx index 23958906..66db3c65 100644 --- a/src/components/TransactionDetails.tsx +++ b/src/components/TransactionDetails.tsx @@ -8,6 +8,7 @@ import { import PaginationLoader from "@components/skeletonLoaders/PaginationLoader"; import { RawTransactionI, TxnNextPageParamsProps } from "@api/types"; import { transformTransactionData } from "shared/transactionDataHelper"; +import { PaginationSource } from "enum/tabsTitle"; interface TransactionsProps { transactions: RawTransactionI[]; @@ -29,6 +30,9 @@ function TxnPagination({ pathname: string; nextPageParams?: TxnNextPageParamsProps; }) { + const source = pathname.includes("/txs") + ? undefined // source not needed for main transaction list page (/txs) + : PaginationSource.Transactions; return ( pathname={pathname} @@ -41,6 +45,7 @@ function TxnPagination({ } : undefined } + source={source} /> ); } diff --git a/src/components/commons/Pagination.tsx b/src/components/commons/Pagination.tsx index ac6250d5..cfae1aa2 100644 --- a/src/components/commons/Pagination.tsx +++ b/src/components/commons/Pagination.tsx @@ -12,6 +12,7 @@ interface PaginationProps { pathname?: string; containerClass?: string; shallow?: boolean; + source?: string; } export default function Pagination({ @@ -19,11 +20,13 @@ export default function Pagination({ pathname, containerClass, shallow, + source, }: PaginationProps): JSX.Element { const router = useRouter(); const pathName = pathname ?? router.pathname; const currentPageNumber = Number(router.query.page_number ?? 1); const nextPageParams = { + ...(source !== undefined && { source }), ...nextPageParamsFromApi, ...{ page_number: currentPageNumber + 1 }, }; @@ -66,6 +69,17 @@ export default function Pagination({ return [pageButton.previous, pageButton.current, pageButton.next]; }; + useEffect(() => { + // Set `source` params on page load + // Update `source` params on tab change + if ( + (source !== undefined && router.query.source === undefined) || + source !== router.query.source + ) { + router.query.source = source; + } + }, [source]); + useEffect(() => { if ( !previousPagesParams.some( diff --git a/src/enum/tabsTitle.ts b/src/enum/tabsTitle.ts index ef0851f4..e783127d 100644 --- a/src/enum/tabsTitle.ts +++ b/src/enum/tabsTitle.ts @@ -6,3 +6,12 @@ export enum AddressContractTabsTitle { TokenTransfers = "Token Transfers", TokenHolders = "Token Holders", } + +export enum PaginationSource { + Transactions = "transactions", + Contract = "contract", + ContractTokens = "contract-tokens", + Logs = "logs", + TokenTransfers = "token-transfers", + TokenHolders = "token-holders", +} diff --git a/src/hooks/useFetchListData.tsx b/src/hooks/useFetchListData.tsx new file mode 100644 index 00000000..361b58a2 --- /dev/null +++ b/src/hooks/useFetchListData.tsx @@ -0,0 +1,45 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { sleep } from "shared/sleep"; +import { PaginationSource } from "enum/tabsTitle"; + +interface FetchDataParams { + addressHash: string; + triggerApiCall: () => any; + source: PaginationSource; +} + +interface FetchDataResponse { + isLoading: boolean; + data: T[]; + nextPage: S | undefined; +} + +export default function useFetchListData({ + addressHash, + triggerApiCall, + source, +}: FetchDataParams): FetchDataResponse { + const [isLoading, setIsLoading] = useState(true); + const [data, setData] = useState([]); + const [nextPage, setNextPage] = useState(); + const router = useRouter(); + + const fetchData = async () => { + setIsLoading(true); + const resp = await triggerApiCall().unwrap(); + setData(resp.items ?? []); + setNextPage(resp.next_page_params); + await sleep(150); // added timeout to prevent flicker + setIsLoading(false); + }; + + useEffect(() => { + const skipDataFetch = router.query.source !== source; + if (!skipDataFetch) { + fetchData(); + } + }, [router.query.page_number, addressHash]); + + return { isLoading, data, nextPage }; +} diff --git a/src/pages/address/_components/ContractTokensList.tsx b/src/pages/address/_components/ContractTokensList.tsx index a118ef8b..a8766fd3 100644 --- a/src/pages/address/_components/ContractTokensList.tsx +++ b/src/pages/address/_components/ContractTokensList.tsx @@ -1,5 +1,4 @@ import clsx from "clsx"; -import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { useNetwork } from "@contexts/NetworkContext"; import { TokenItemI, TokensListPageParamsProps } from "@api/types"; @@ -10,7 +9,8 @@ import { SkeletonLoaderScreen, } from "@components/skeletonLoaders/SkeletonLoader"; import { useGetContractTokensMutation } from "@store/token"; -import { sleep } from "shared/sleep"; +import useFetchListData from "@hooks/useFetchListData"; +import { PaginationSource } from "enum/tabsTitle"; import DetailRowTitle from "./DetailRowTitle"; import ContractTokenRow, { TokenTableFixedTitle } from "./ContractTokenRow"; @@ -20,29 +20,24 @@ interface TokenDetailsProps { export default function ContractTokensList({ addressHash }: TokenDetailsProps) { const { connection } = useNetwork(); - const [tokens, setTokens] = useState([]); - const [nextPage, setNextPage] = useState(); - const [isLoading, setIsLoading] = useState(true); const [trigger] = useGetContractTokensMutation(); const router = useRouter(); - const params = router.query; - const fetchTokens = async () => { - setIsLoading(true); - const tokenList = await trigger({ - network: connection, - addressHash, - queryParams: params, - }).unwrap(); - setTokens(tokenList.items); - setNextPage(tokenList.next_page_params as TokensListPageParamsProps); - await sleep(150); // added timeout to prevent flicker - setIsLoading(false); - }; - useEffect(() => { - fetchTokens(); - }, [params.page_number, addressHash]); + const { + data: tokens, + isLoading, + nextPage, + } = useFetchListData({ + addressHash, + source: PaginationSource.ContractTokens, + triggerApiCall: () => + trigger({ + network: connection, + addressHash, + queryParams: params, + }), + }); if (!isLoading && tokens.length === 0) { return
No contract tokens
; @@ -129,6 +124,7 @@ function TokensListPagination({ pathname={`/address/${addressHash}`} nextPageParams={nextPageParams} + source={PaginationSource.ContractTokens} shallow /> diff --git a/src/pages/address/_components/LogsList.tsx b/src/pages/address/_components/LogsList.tsx index 8955f0e1..84fe3d84 100644 --- a/src/pages/address/_components/LogsList.tsx +++ b/src/pages/address/_components/LogsList.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useState } from "react"; +import { Fragment } from "react"; import { useRouter } from "next/router"; import { useGetAddressLogsMutation, @@ -13,35 +13,31 @@ import { SkeletonLoader, SkeletonLoaderScreen, } from "@components/skeletonLoaders/SkeletonLoader"; -import { sleep } from "shared/sleep"; +import useFetchListData from "@hooks/useFetchListData"; +import { PaginationSource } from "enum/tabsTitle"; export default function LogsList({ addressHash }: { addressHash: string }) { const { connection } = useNetwork(); - const [logs, setLogs] = useState([]); - const [nextPage, setNextPage] = useState(); - const [isLoading, setIsLoading] = useState(true); const [trigger] = useGetAddressLogsMutation(); const router = useRouter(); const params = router.query; - const fetchLogs = async () => { - setIsLoading(true); - const data = await trigger({ - network: connection, - itemsCount: params.items_count as string, - blockNumber: params.block_number as string, - index: params.index as string, - addressHash, - }).unwrap(); - setLogs(data.items); - setNextPage(data.next_page_params); - await sleep(150); // added timeout to prevent flicker - setIsLoading(false); - }; - - useEffect(() => { - fetchLogs(); - }, [params.page_number, addressHash]); + const { + data: logs, + isLoading, + nextPage, + } = useFetchListData({ + addressHash, + source: PaginationSource.Logs, + triggerApiCall: () => + trigger({ + network: connection, + itemsCount: params.items_count as string, + blockNumber: params.block_number as string, + index: params.index as string, + addressHash, + }), + }); if (!isLoading && logs.length === 0) { return
No logs
; @@ -146,6 +142,7 @@ function LogsPagination({ } : undefined } + source={PaginationSource.Logs} shallow /> diff --git a/src/pages/address/_components/TokenHoldersList.tsx b/src/pages/address/_components/TokenHoldersList.tsx index 06a041a2..a3f2b7ec 100644 --- a/src/pages/address/_components/TokenHoldersList.tsx +++ b/src/pages/address/_components/TokenHoldersList.tsx @@ -1,5 +1,4 @@ import BigNumber from "bignumber.js"; -import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { useNetwork } from "@contexts/NetworkContext"; import { @@ -16,9 +15,10 @@ import Pagination from "@components/commons/Pagination"; import LinkText from "@components/commons/LinkText"; import { truncateTextFromMiddle } from "shared/textHelper"; import NumericFormat from "@components/commons/NumericFormat"; -import { sleep } from "shared/sleep"; import { GWEI_DECIMAL } from "shared/constants"; import { formatUnits } from "viem"; +import useFetchListData from "@hooks/useFetchListData"; +import { PaginationSource } from "enum/tabsTitle"; export default function TokenHoldersList({ addressHash, @@ -26,30 +26,25 @@ export default function TokenHoldersList({ addressHash: string; }) { const { connection } = useNetwork(); - const [holders, setHolders] = useState([]); - const [nextPage, setNextPage] = useState(); - const [isLoading, setIsLoading] = useState(true); const [trigger] = useGetTokenHoldersMutation(); const router = useRouter(); - const params = router.query; - const fetchTokenHolders = async () => { - setIsLoading(true); - const data = await trigger({ - network: connection, - tokenId: addressHash, - itemsCount: params.items_count as string, - value: params.value ? BigInt(Number(params.value)).toString() : "", - }).unwrap(); - setHolders(data.items); - setNextPage(data.next_page_params); - await sleep(150); // added timeout to prevent flicker - setIsLoading(false); - }; - useEffect(() => { - fetchTokenHolders(); - }, [params.page_number, addressHash]); + const { + data: holders, + isLoading, + nextPage, + } = useFetchListData({ + addressHash, + source: PaginationSource.TokenHolders, + triggerApiCall: () => + trigger({ + network: connection, + tokenId: addressHash, + itemsCount: params.items_count as string, + value: params.value ? BigInt(Number(params.value)).toString() : "", + }), + }); if (!isLoading && holders.length === 0) { return
No token holders
; @@ -117,6 +112,7 @@ function HoldersPagination({ } : undefined } + source={PaginationSource.TokenHolders} shallow /> diff --git a/src/pages/address/_components/TokenTransfersList.tsx b/src/pages/address/_components/TokenTransfersList.tsx index 126d894a..d852b477 100644 --- a/src/pages/address/_components/TokenTransfersList.tsx +++ b/src/pages/address/_components/TokenTransfersList.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { useNetwork } from "@contexts/NetworkContext"; import { @@ -15,9 +14,10 @@ import { import TransactionRow from "@components/commons/TransactionRow"; import { getTransactionTypeFromTokenTransfers } from "shared/transactionDataHelper"; import { getTimeAgo } from "shared/durationHelper"; -import { sleep } from "shared/sleep"; import { GWEI_DECIMAL } from "shared/constants"; import { formatUnits } from "viem"; +import useFetchListData from "@hooks/useFetchListData"; +import { PaginationSource } from "enum/tabsTitle"; export default function TokenTransfersList({ addressHash, @@ -25,30 +25,25 @@ export default function TokenTransfersList({ addressHash: string; }) { const { connection } = useNetwork(); - const [transfers, setTransfers] = useState([]); - const [nextPage, setNextPage] = useState(); - const [isLoading, setIsLoading] = useState(true); const [trigger] = useGetTokenTransfersMutation(); const router = useRouter(); - const params = router.query; - const fetchTokenTransfers = async () => { - setIsLoading(true); - const data = await trigger({ - network: connection, - tokenId: addressHash, - blockNumber: params.block_number as string, - index: params.index as string, - }).unwrap(); - setTransfers(data.items); - setNextPage(data.next_page_params); - await sleep(150); // added timeout to prevent flicker - setIsLoading(false); - }; - useEffect(() => { - fetchTokenTransfers(); - }, [params.page_number, addressHash]); + const { + data: transfers, + isLoading, + nextPage, + } = useFetchListData({ + addressHash, + source: PaginationSource.TokenTransfers, + triggerApiCall: () => + trigger({ + network: connection, + tokenId: addressHash, + blockNumber: params.block_number as string, + index: params.index as string, + }), + }); if (!isLoading && transfers.length === 0) { return
No token transfers
; @@ -123,6 +118,7 @@ function TransfersPagination({ } : undefined } + source={PaginationSource.TokenTransfers} shallow /> diff --git a/src/pages/address/_components/TransactionsList.tsx b/src/pages/address/_components/TransactionsList.tsx index 534e04a7..a96dec74 100644 --- a/src/pages/address/_components/TransactionsList.tsx +++ b/src/pages/address/_components/TransactionsList.tsx @@ -1,10 +1,10 @@ -import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import TransactionDetails from "@components/TransactionDetails"; import { useNetwork } from "@contexts/NetworkContext"; import { useGetAddressTransactionsMutation } from "@store/address"; import { RawTransactionI, TxnNextPageParamsProps } from "@api/types"; -import { sleep } from "shared/sleep"; +import useFetchListData from "@hooks/useFetchListData"; +import { PaginationSource } from "enum/tabsTitle"; export default function TransactionsList({ addressHash, @@ -12,37 +12,31 @@ export default function TransactionsList({ addressHash: string; }) { const { connection } = useNetwork(); - const [transactions, setTransactions] = useState([]); - const [nextPageParams, setNextPageParams] = - useState(); - const [isLoading, setIsLoading] = useState(true); const [trigger] = useGetAddressTransactionsMutation(); const router = useRouter(); - const params = router.query; - const getTransactions = async () => { - setIsLoading(true); - const data = await trigger({ - network: connection, - itemsCount: params.items_count as string, - blockNumber: params.block_number as string, - index: params.index as string, - addressHash, - }).unwrap(); - setTransactions(data.items); - setNextPageParams(data.next_page_params); - await sleep(150); // added timeout to prevent flicker - setIsLoading(false); - }; - useEffect(() => { - getTransactions(); - }, [params.page_number, addressHash]); + const { + data: transactions, + isLoading, + nextPage, + } = useFetchListData({ + addressHash, + source: PaginationSource.Transactions, + triggerApiCall: () => + trigger({ + network: connection, + itemsCount: params.items_count as string, + blockNumber: params.block_number as string, + index: params.index as string, + addressHash, + }), + }); return (