From 8c2a8d74e268d8a1fa0708f2e620f49bc5a3ad91 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Thu, 1 May 2025 15:35:21 -0700 Subject: [PATCH 1/5] Fix loop indexing logic It was possible under some situations for the first query to fail this condition and either not load the first or last page. --- src/components/scenes/CoinRankingScene.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/scenes/CoinRankingScene.tsx b/src/components/scenes/CoinRankingScene.tsx index f8e5a828c17..ab2b08dc827 100644 --- a/src/components/scenes/CoinRankingScene.tsx +++ b/src/components/scenes/CoinRankingScene.tsx @@ -157,7 +157,7 @@ const CoinRankingComponent = (props: Props) => { try { // Catch up to the total required items - while (startIndex < requestDataSize - QUERY_PAGE_SIZE) { + while (startIndex <= requestDataSize - QUERY_PAGE_SIZE + 1) { const url = `v2/coinrank?fiatCode=iso:${coingeckoFiat}&start=${startIndex}&length=${QUERY_PAGE_SIZE}` const response = await fetchRates(url).then(maybeAbort) if (!response.ok) { From b8ecbd7d184b8952c9746db2575595358469e7ad Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Thu, 1 May 2025 15:35:55 -0700 Subject: [PATCH 2/5] Clarify comment --- src/components/scenes/CoinRankingScene.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/scenes/CoinRankingScene.tsx b/src/components/scenes/CoinRankingScene.tsx index ab2b08dc827..6e489fa18e0 100644 --- a/src/components/scenes/CoinRankingScene.tsx +++ b/src/components/scenes/CoinRankingScene.tsx @@ -192,7 +192,7 @@ const CoinRankingComponent = (props: Props) => { return abort }, [queryLoop, requestDataSize]) - // Subscribe to changes to the current data set: + // Subscribe to changes to coingeckoFiat and update the periodic refresh React.useEffect(() => { let abort = () => {} // Refresh from the beginning periodically From 6afc0609681fb7f0642aae9b712010e361aa0caa Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Thu, 1 May 2025 15:36:24 -0700 Subject: [PATCH 3/5] Add extra logging --- src/components/scenes/CoinRankingScene.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/scenes/CoinRankingScene.tsx b/src/components/scenes/CoinRankingScene.tsx index 6e489fa18e0..5144091d541 100644 --- a/src/components/scenes/CoinRankingScene.tsx +++ b/src/components/scenes/CoinRankingScene.tsx @@ -176,7 +176,7 @@ const CoinRankingComponent = (props: Props) => { startIndex += QUERY_PAGE_SIZE } } catch (e: any) { - console.warn(`Error during data fetch: ${e.message}`) + console.warn(`Error during data fetch: ${e.message}, ${coingeckoFiat}, ${startIndex}, ${requestDataSize}`) } setDataSize(coinRankingDatas.length) From a4e0cf72b04d598c74f3a82979dab746566698d8 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Thu, 1 May 2025 15:40:03 -0700 Subject: [PATCH 4/5] Add a loader when the list is empty --- CHANGELOG.md | 1 + src/components/scenes/CoinRankingScene.tsx | 30 ++++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5d5a8f4058..f442b465c9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - added: "Asset Settings" option to `WalletListMenuModal` - added: Sonic support - added: Bitcoin Testnet4 support +- added: Loader on `CoinRankingScene` - changed: Buy/sell removed for UK - changed: `GettingStartedScene` "Getting Started" button repurposed to progress through hero pages - changed: `GettingStartedScene` "Swipe to Learn More" copy updated to "Learn More" and is now tappable diff --git a/src/components/scenes/CoinRankingScene.tsx b/src/components/scenes/CoinRankingScene.tsx index 5144091d541..b5688322a9d 100644 --- a/src/components/scenes/CoinRankingScene.tsx +++ b/src/components/scenes/CoinRankingScene.tsx @@ -220,8 +220,8 @@ const CoinRankingComponent = (props: Props) => { } }, [coingeckoFiat /* reset subscription on fiat change */, queryLoop]) - const listdata: number[] = React.useMemo(() => { - debugLog(LOG_COINRANK, `Updating listdata dataSize=${dataSize} searchText=${searchText}`) + const listData: number[] = React.useMemo(() => { + debugLog(LOG_COINRANK, `Updating listData dataSize=${dataSize} searchText=${searchText}`) const out = [] for (let i = 0; i < dataSize; i++) { const cr = coinRankingDatas[i] @@ -282,17 +282,21 @@ const CoinRankingComponent = (props: Props) => { - + {listData.length === 0 ? ( + + ) : ( + + )} )} From 423ae98672dc3392e943a5265279528ed103b678 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Thu, 1 May 2025 15:46:04 -0700 Subject: [PATCH 5/5] Fix coinrank caching behavior Previously, we were always clearing the cache intentionally on a fiat change, but unintentionally also on unmounting the scene, so the cache wasn't utilized when re-mounting the scene. This change requires keeping track of the scene's last known fiat in case it changes while the scene is unmounted, which should result in a clearing of the cache upon re-mount. --- src/components/scenes/CoinRankingScene.tsx | 39 +++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/components/scenes/CoinRankingScene.tsx b/src/components/scenes/CoinRankingScene.tsx index b5688322a9d..22a40d0075d 100644 --- a/src/components/scenes/CoinRankingScene.tsx +++ b/src/components/scenes/CoinRankingScene.tsx @@ -18,6 +18,7 @@ import { fetchRates } from '../../util/network' import { EdgeAnim, MAX_LIST_ITEMS_ANIM } from '../common/EdgeAnim' import { EdgeTouchableOpacity } from '../common/EdgeTouchableOpacity' import { SceneWrapper } from '../common/SceneWrapper' +import { FillLoader } from '../progress-indicators/FillLoader' import { CoinRankRow } from '../rows/CoinRankRow' import { showDevError } from '../services/AirshipInstance' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' @@ -27,6 +28,12 @@ import { SearchFooter } from '../themed/SearchFooter' const coinRanking: CoinRanking = { coinRankingDatas: [] } +/** + * Track changes that occurred to fiat while scene is unmounted. Don't react to + * changes to this value. + */ +let lastSceneFiat: string + const QUERY_PAGE_SIZE = 30 const LISTINGS_REFRESH_INTERVAL = 30000 @@ -54,20 +61,36 @@ const CoinRankingComponent = (props: Props) => { const { navigation } = props const dispatch = useDispatch() - /** The user's fiat setting, falling back to USD if not supported. */ - const coingeckoFiat = useSelector(state => getCoingeckoFiat(state)) + const { coinRankingDatas } = coinRanking - const mounted = React.useRef(true) const lastStartIndex = React.useRef(1) const [requestDataSize, setRequestDataSize] = React.useState(QUERY_PAGE_SIZE) - const [dataSize, setDataSize] = React.useState(0) + const [dataSize, setDataSize] = React.useState(coinRankingDatas.length) const [searchText, setSearchText] = React.useState('') const [isSearching, setIsSearching] = React.useState(false) const [percentChangeTimeFrame, setPercentChangeTimeFrame] = React.useState('hours24') const [assetSubText, setPriceSubText] = React.useState('marketCap') const [footerHeight, setFooterHeight] = React.useState() + /** The user's fiat setting, falling back to USD if not supported. */ + const coingeckoFiat = useSelector(state => { + const currentCoinGeckoFiat = getCoingeckoFiat(state) + if (lastSceneFiat == null) { + lastSceneFiat = currentCoinGeckoFiat + } + + // Whenever we see a different fiat, clear the cache. We want to do this + // here as close to the site of the state change as possible to ensure this + // happens before anything else. + if (lastSceneFiat != null && currentCoinGeckoFiat !== lastSceneFiat) { + debugLog(LOG_COINRANK, 'clearing cache') + lastSceneFiat = currentCoinGeckoFiat + coinRanking.coinRankingDatas = [] + } + return currentCoinGeckoFiat + }) + const handleScroll = useSceneScrollHandler() const extraData = React.useMemo( @@ -75,8 +98,6 @@ const CoinRankingComponent = (props: Props) => { [assetSubText, coingeckoFiat, percentChangeTimeFrame] ) - const { coinRankingDatas } = coinRanking - const renderItem = (itemObj: ListRenderItemInfo) => { const { index, item } = itemObj const currencyCode = coinRankingDatas[index]?.currencyCode ?? 'NO_CURRENCY_CODE' @@ -138,12 +159,6 @@ const CoinRankingComponent = (props: Props) => { setFooterHeight(height) }) - React.useEffect(() => { - return () => { - mounted.current = false - } - }, []) - React.useEffect(() => { return navigation.addListener('focus', () => { dispatch(checkEnabledExchanges())