-
Notifications
You must be signed in to change notification settings - Fork 38
Feature/move history fetch to bg #2273
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
Changes from all commits
8c0c8f7
c5b1ff2
15dbf22
8b2ed38
9e0a758
ba9a7b2
a88c762
e7a440a
a2e7cd3
6b12c5e
e27c594
08e4bb3
6859fe0
d82ace3
3bd7f68
eca68d2
8324043
6144ebe
36b5331
67be1be
dfcb486
a194f20
6a11925
5e91240
bf2292b
8b64221
a5a3433
f0bb85f
69723fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,40 @@ | ||
| import { useReducer } from "react"; | ||
| import { useDispatch, useSelector } from "react-redux"; | ||
|
|
||
| import { historySelector, saveHistoryForAccount } from "popup/ducks/cache"; | ||
| import { getAccountHistory } from "@shared/api/internal"; | ||
| import { NetworkDetails } from "@shared/constants/stellar"; | ||
| import { initialState, reducer } from "helpers/request"; | ||
| import { HorizonOperation } from "@shared/api/types"; | ||
| import { AppDispatch } from "popup/App"; | ||
|
|
||
| export type HistoryResponse = HorizonOperation[]; | ||
|
|
||
| function useGetHistory() { | ||
| const reduxDispatch = useDispatch<AppDispatch>(); | ||
| const [state, dispatch] = useReducer( | ||
| reducer<HistoryResponse, unknown>, | ||
| initialState, | ||
| ); | ||
| const cachedHistory = useSelector(historySelector); | ||
|
|
||
| const fetchData = async ( | ||
| publicKey: string, | ||
| networkDetails: NetworkDetails, | ||
| useCache = false, | ||
| ): Promise<HistoryResponse | Error> => { | ||
| dispatch({ type: "FETCH_DATA_START" }); | ||
| try { | ||
| const data = await getAccountHistory(publicKey, networkDetails); | ||
| const cachedHistoryData = | ||
| cachedHistory[networkDetails.network]?.[publicKey]; | ||
| const data = | ||
| useCache && cachedHistoryData | ||
| ? cachedHistoryData | ||
| : await getAccountHistory(publicKey, networkDetails); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. following a similar pattern to account balance caching |
||
| dispatch({ type: "FETCH_DATA_SUCCESS", payload: data }); | ||
| reduxDispatch( | ||
| saveHistoryForAccount({ publicKey, history: data, networkDetails }), | ||
| ); | ||
| return data; | ||
| } catch (error) { | ||
| dispatch({ type: "FETCH_DATA_ERROR", payload: error }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { useReducer } from "react"; | ||
| import { useDispatch, useSelector } from "react-redux"; | ||
|
|
||
| import { tokenDetailsSelector, saveTokenDetails } from "popup/ducks/cache"; | ||
| import { getTokenDetails } from "@shared/api/internal"; | ||
| import { initialState, reducer } from "helpers/request"; | ||
| import { NetworkDetails } from "@shared/constants/stellar"; | ||
| import { AppDispatch, store } from "popup/App"; | ||
|
|
||
| export type TokenDetailsResponse = { | ||
| decimals: number; | ||
| symbol: string; | ||
| name: string; | ||
| } | null; | ||
|
|
||
| function useTokenDetails() { | ||
| const reduxDispatch = useDispatch<AppDispatch>(); | ||
| const [state, dispatch] = useReducer( | ||
| reducer<TokenDetailsResponse, unknown>, | ||
| initialState, | ||
| ); | ||
| const cachedTokenDetails = useSelector(tokenDetailsSelector); | ||
|
|
||
| const fetchData = async ({ | ||
| contractId, | ||
| useCache = true, | ||
| publicKey, | ||
| networkDetails, | ||
| }: { | ||
| contractId: string; | ||
| useCache?: boolean; | ||
| publicKey: string; | ||
| networkDetails: NetworkDetails; | ||
| }): Promise<TokenDetailsResponse | Error> => { | ||
| dispatch({ type: "FETCH_DATA_START" }); | ||
| try { | ||
| /* | ||
| Unlike the other cache hooks, this hook may be called multiple times within one render. | ||
| For example, when constructing the history rows, this hook can be called many times as we iterate over the history items. | ||
| If we have cached token details earlier in the loop, we won't have access to the update redux state until the next render. | ||
| To workaround this, we will also check the redux state manually here rather than waiting for the next render to | ||
| update the selector hook for us. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example: In my account history, I have 10 transfers using the same custom token. The getHistoryData hook will run once on first render and then call this method 10 times as we iterate over the history items. However, because we're still in the first render, the useSelector hook won't have access to the updated redux state yet. This will result in |
||
| */ | ||
| const cachedTokenDetailsData = | ||
| cachedTokenDetails[contractId] || | ||
| store.getState().cache.tokenDetails[contractId]; | ||
|
|
||
| const data = | ||
| useCache && cachedTokenDetailsData | ||
| ? cachedTokenDetailsData | ||
| : await getTokenDetails({ contractId, publicKey, networkDetails }); | ||
| if (data && Object.keys(data).length) { | ||
| reduxDispatch(saveTokenDetails({ contractId, ...data })); | ||
| } | ||
| dispatch({ type: "FETCH_DATA_SUCCESS", payload: data }); | ||
| return data; | ||
| } catch (error) { | ||
| dispatch({ type: "FETCH_DATA_ERROR", payload: error }); | ||
| throw new Error("Failed to fetch token details", { cause: error }); | ||
| } | ||
| }; | ||
|
|
||
| return { | ||
| state, | ||
| fetchData, | ||
| }; | ||
| } | ||
|
|
||
| export { useTokenDetails }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; | |
| import { useSelector } from "react-redux"; | ||
| import { BigNumber } from "bignumber.js"; | ||
| import { useTranslation } from "react-i18next"; | ||
| import { CopyText, Icon, Link } from "@stellar/design-system"; | ||
| import { CopyText, Icon, Link, Loader } from "@stellar/design-system"; | ||
|
|
||
| import { ApiTokenPrice, ApiTokenPrices } from "@shared/api/types"; | ||
| import { NetworkDetails } from "@shared/constants/stellar"; | ||
|
|
@@ -40,11 +40,53 @@ import { | |
| LiquidityPoolShareAsset, | ||
| } from "@shared/api/types/account-balance"; | ||
| import { OperationDataRow } from "popup/views/AccountHistory/hooks/useGetHistoryData"; | ||
| import { AccountHistoryData } from "popup/views/Account/hooks/useGetAccountHistoryData"; | ||
| import { AppDataType } from "helpers/hooks/useGetAppData"; | ||
|
|
||
| import "./styles.scss"; | ||
|
|
||
| const AssetDetailOperations = ({ | ||
| filteredAssetOperations, | ||
| accountBalances, | ||
| publicKey, | ||
| networkDetails, | ||
| setActiveAssetId, | ||
| }: { | ||
| filteredAssetOperations: OperationDataRow[]; | ||
| accountBalances: AccountBalances; | ||
| publicKey: string; | ||
| networkDetails: NetworkDetails; | ||
| setActiveAssetId: (id: string) => void; | ||
| }) => { | ||
| const { t } = useTranslation(); | ||
| return ( | ||
| <> | ||
| {filteredAssetOperations.length ? ( | ||
| <div className="AssetDetail__list" data-testid="AssetDetail__list"> | ||
| <> | ||
| {filteredAssetOperations.map((operation) => ( | ||
| <HistoryItem | ||
| key={operation.id} | ||
| accountBalances={accountBalances} | ||
| operation={operation} | ||
| publicKey={publicKey} | ||
| networkDetails={networkDetails} | ||
| setActiveHistoryDetailId={() => setActiveAssetId(operation.id)} | ||
| /> | ||
| ))} | ||
| </> | ||
| </div> | ||
| ) : ( | ||
| <div className="AssetDetail__empty" data-testid="AssetDetail__empty"> | ||
| {t("No transactions to show")} | ||
| </div> | ||
| )} | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| interface AssetDetailProps { | ||
| assetOperations: OperationDataRow[]; | ||
| historyData: AccountHistoryData | null; | ||
| accountBalances: AccountBalances; | ||
| networkDetails: NetworkDetails; | ||
| publicKey: string; | ||
|
|
@@ -55,7 +97,7 @@ interface AssetDetailProps { | |
| } | ||
|
|
||
| export const AssetDetail = ({ | ||
| assetOperations, | ||
| historyData, | ||
| accountBalances, | ||
| networkDetails, | ||
| publicKey, | ||
|
|
@@ -65,10 +107,10 @@ export const AssetDetail = ({ | |
| tokenPrices, | ||
| }: AssetDetailProps) => { | ||
| const { t } = useTranslation(); | ||
| const { isHideDustEnabled } = useSelector(settingsSelector); | ||
| const [optionsOpen, setOptionsOpen] = React.useState(false); | ||
| const activeOptionsRef = useRef<HTMLDivElement>(null); | ||
| const isNative = selectedAsset === "native"; | ||
| const { isHideDustEnabled } = useSelector(settingsSelector); | ||
|
|
||
| useEffect(() => { | ||
| function handleClickOutside(event: MouseEvent) { | ||
|
|
@@ -129,27 +171,33 @@ export const AssetDetail = ({ | |
| assetIssuer, | ||
| }); | ||
|
|
||
| if (!assetOperations && !isSorobanAsset) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm removing this This was supposed to be blocking showing history for custom tokens, but wasn't actually working. We can see history for all classic and custom tokens |
||
| if (historyData?.type === AppDataType.REROUTE) { | ||
| return null; | ||
| } | ||
|
|
||
| const sortedAssetOperations = assetOperations.filter((operation) => { | ||
| if (operation.metadata.isDustPayment && isHideDustEnabled) { | ||
| return false; | ||
| } | ||
| const assetOperations = | ||
| historyData?.operationsByAsset?.[selectedAsset] || null; | ||
|
|
||
| return true; | ||
| }); | ||
| let filteredAssetOperations = null; | ||
| let activeOperation = null; | ||
|
|
||
| if (assetOperations) { | ||
| filteredAssetOperations = assetOperations.filter((operation) => { | ||
| if (operation.metadata.isDustPayment && isHideDustEnabled) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| }); | ||
| activeOperation = | ||
| filteredAssetOperations.find((op) => op.id === activeAssetId) || null; | ||
| } | ||
|
|
||
| if (assetIssuer && !assetDomain && !assetError && !isSorobanAsset) { | ||
| // if we have an asset issuer, wait until we have the asset domain before continuing | ||
| return <Loading />; | ||
| } | ||
|
|
||
| const activeOperation = sortedAssetOperations.find( | ||
| (op) => op.id === activeAssetId, | ||
| ); | ||
|
|
||
| const isStellarExpertSupported = | ||
| isMainnet(networkDetails) || isTestnet(networkDetails); | ||
| const stellarExpertAssetLinkSlug = isSorobanBalance(selectedBalance) | ||
|
|
@@ -296,30 +344,21 @@ export const AssetDetail = ({ | |
| </div> | ||
| </div> | ||
| </div> | ||
| {sortedAssetOperations.length ? ( | ||
| <div className="AssetDetail__list" data-testid="AssetDetail__list"> | ||
| <> | ||
| {sortedAssetOperations.map((operation) => ( | ||
| <HistoryItem | ||
| key={operation.id} | ||
| accountBalances={accountBalances} | ||
| operation={operation} | ||
| publicKey={publicKey} | ||
| networkDetails={networkDetails} | ||
| setActiveHistoryDetailId={() => | ||
| setActiveAssetId(operation.id) | ||
| } | ||
| /> | ||
| ))} | ||
| </> | ||
| </div> | ||
| ) : ( | ||
| {filteredAssetOperations === null ? ( | ||
| <div | ||
| className="AssetDetail__empty" | ||
| data-testid="AssetDetail__empty" | ||
| className="AssetDetail__list AssetDetail__list--loading" | ||
| data-testid="AssetDetail__list__loader" | ||
| > | ||
| {t("No transactions to show")} | ||
| <Loader /> | ||
| </div> | ||
| ) : ( | ||
| <AssetDetailOperations | ||
| filteredAssetOperations={filteredAssetOperations} | ||
| accountBalances={accountBalances} | ||
| publicKey={publicKey} | ||
| networkDetails={networkDetails} | ||
| setActiveAssetId={setActiveAssetId} | ||
| /> | ||
| )} | ||
| </div> | ||
| </View.Content> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test is a bit flakey - I believe just waiting a little longer for the UI to re-render should solve this