Skip to content

Jon/fix/coinrank-loading #5544

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

Merged
merged 5 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 47 additions & 28 deletions src/components/scenes/CoinRankingScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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

Expand Down Expand Up @@ -54,29 +61,43 @@ 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<boolean>(true)
const lastStartIndex = React.useRef<number>(1)

const [requestDataSize, setRequestDataSize] = React.useState<number>(QUERY_PAGE_SIZE)
const [dataSize, setDataSize] = React.useState<number>(0)
const [dataSize, setDataSize] = React.useState<number>(coinRankingDatas.length)
const [searchText, setSearchText] = React.useState<string>('')
const [isSearching, setIsSearching] = React.useState<boolean>(false)
const [percentChangeTimeFrame, setPercentChangeTimeFrame] = React.useState<PercentChangeTimeFrame>('hours24')
const [assetSubText, setPriceSubText] = React.useState<AssetSubText>('marketCap')
const [footerHeight, setFooterHeight] = React.useState<number | undefined>()

/** 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(
() => ({ assetSubText, supportedFiatSetting: coingeckoFiat, percentChangeTimeFrame }),
[assetSubText, coingeckoFiat, percentChangeTimeFrame]
)

const { coinRankingDatas } = coinRanking

const renderItem = (itemObj: ListRenderItemInfo<number>) => {
const { index, item } = itemObj
const currencyCode = coinRankingDatas[index]?.currencyCode ?? 'NO_CURRENCY_CODE'
Expand Down Expand Up @@ -138,12 +159,6 @@ const CoinRankingComponent = (props: Props) => {
setFooterHeight(height)
})

React.useEffect(() => {
return () => {
mounted.current = false
}
}, [])

React.useEffect(() => {
return navigation.addListener('focus', () => {
dispatch(checkEnabledExchanges())
Expand All @@ -157,7 +172,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this fix an issue where the last item wasn't included for some reason? It's hard to know what the problem was and therefore review a fix if you don't know what the problem is. Could you maybe include the problem in the commit body?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and this also fixes the issue where the query doesn't start immediately under some situations.

You can see in the changes you reviewed previously, I had a condition startIndex === 1 || ... which resolved the "not starting immediately" issue, but didn't fix the new page requests sometimes not fetching new rows.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll edit the commit message

const url = `v2/coinrank?fiatCode=iso:${coingeckoFiat}&start=${startIndex}&length=${QUERY_PAGE_SIZE}`
const response = await fetchRates(url).then(maybeAbort)
if (!response.ok) {
Expand All @@ -176,7 +191,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}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, but Error during data fetch: ${e.message} (${coingeckoFiat} from ${startIndex} to ${requestDataSize}) might be a clearer output to read cause it formats the numbers with prefixes like from and to also puts this in a separate parenthetical structure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch yeah I didn't update this to be publicly readable from my own testing

}

setDataSize(coinRankingDatas.length)
Expand All @@ -192,7 +207,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
Expand Down Expand Up @@ -220,8 +235,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]
Expand Down Expand Up @@ -282,17 +297,21 @@ const CoinRankingComponent = (props: Props) => {
</View>
<DividerLine marginRem={[0, 0, 0, 1]} />
<View style={{ ...undoInsetStyle, marginTop: 0 }}>
<Animated.FlatList
contentContainerStyle={{ ...insetStyle, paddingTop: 0 }}
data={listdata}
extraData={extraData}
keyboardDismissMode="on-drag"
onEndReachedThreshold={1}
onEndReached={handleEndReached}
onScroll={handleScroll}
renderItem={renderItem}
scrollIndicatorInsets={SCROLL_INDICATOR_INSET_FIX}
/>
{listData.length === 0 ? (
<FillLoader />
) : (
<Animated.FlatList
contentContainerStyle={{ ...insetStyle, paddingTop: 0 }}
data={listData}
extraData={extraData}
keyboardDismissMode="on-drag"
onEndReachedThreshold={1}
onEndReached={handleEndReached}
onScroll={handleScroll}
renderItem={renderItem}
scrollIndicatorInsets={SCROLL_INDICATOR_INSET_FIX}
/>
)}
</View>
</>
)}
Expand Down
Loading