-
Notifications
You must be signed in to change notification settings - Fork 265
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
base: develop
Are you sure you want to change the base?
Jon/fix/coinrank-loading #5544
Changes from all commits
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 |
---|---|---|
|
@@ -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,9 @@ import { SearchFooter } from '../themed/SearchFooter' | |
|
||
const coinRanking: CoinRanking = { coinRankingDatas: [] } | ||
|
||
/** Track changes that occurred to fiat while scene is unmounted */ | ||
let lastSceneFiat: string | ||
|
||
const QUERY_PAGE_SIZE = 30 | ||
const LISTINGS_REFRESH_INTERVAL = 30000 | ||
|
||
|
@@ -54,14 +58,16 @@ const CoinRankingComponent = (props: Props) => { | |
const { navigation } = props | ||
const dispatch = useDispatch() | ||
|
||
const { coinRankingDatas } = coinRanking | ||
|
||
/** The user's fiat setting, falling back to USD if not supported. */ | ||
const coingeckoFiat = useSelector(state => getCoingeckoFiat(state)) | ||
|
||
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') | ||
|
@@ -75,8 +81,6 @@ const CoinRankingComponent = (props: Props) => { | |
[assetSubText, coingeckoFiat, percentChangeTimeFrame] | ||
) | ||
|
||
const { coinRankingDatas } = coinRanking | ||
|
||
const renderItem = (itemObj: ListRenderItemInfo<number>) => { | ||
const { index, item } = itemObj | ||
const currencyCode = coinRankingDatas[index]?.currencyCode ?? 'NO_CURRENCY_CODE' | ||
|
@@ -139,9 +143,22 @@ const CoinRankingComponent = (props: Props) => { | |
}) | ||
|
||
React.useEffect(() => { | ||
if (lastSceneFiat != null && lastSceneFiat !== coingeckoFiat) { | ||
// Clear cache if we changed the fiat while outside of this scene | ||
debugLog(LOG_COINRANK, 'Clearing coinRankingDatas cache') | ||
coinRanking.coinRankingDatas = [] | ||
lastStartIndex.current = 1 | ||
setDataSize(0) | ||
setRequestDataSize(QUERY_PAGE_SIZE) | ||
} | ||
|
||
return () => { | ||
mounted.current = false | ||
|
||
// Queries should update this, but just in case: | ||
lastSceneFiat = coingeckoFiat | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []) | ||
|
||
React.useEffect(() => { | ||
|
@@ -156,8 +173,9 @@ const CoinRankingComponent = (props: Props) => { | |
debugLog(LOG_COINRANK, `queryLoop(start: ${startIndex})`) | ||
|
||
try { | ||
// Catch up to the total required items | ||
while (startIndex < requestDataSize - QUERY_PAGE_SIZE) { | ||
// Catch up to the total required items. Always fetch if requesting the | ||
// first page. | ||
while (startIndex === 1 || startIndex < requestDataSize - QUERY_PAGE_SIZE) { | ||
const url = `v2/coinrank?fiatCode=iso:${coingeckoFiat}&start=${startIndex}&length=${QUERY_PAGE_SIZE}` | ||
const response = await fetchRates(url).then(maybeAbort) | ||
if (!response.ok) { | ||
|
@@ -166,6 +184,7 @@ const CoinRankingComponent = (props: Props) => { | |
break | ||
} | ||
const replyJson = await response.json().then(maybeAbort) | ||
lastSceneFiat = coingeckoFiat | ||
const listings = asCoinranking(replyJson) | ||
for (let i = 0; i < listings.data.length; i++) { | ||
const rankIndex = startIndex - 1 + i | ||
|
@@ -194,33 +213,38 @@ const CoinRankingComponent = (props: Props) => { | |
|
||
// Subscribe to changes to the current data set: | ||
React.useEffect(() => { | ||
let abort = () => {} | ||
let loopAbort = () => {} | ||
// Refresh from the beginning periodically | ||
let timeoutId = setTimeout(loopBody, LISTINGS_REFRESH_INTERVAL) | ||
function loopBody() { | ||
debugLog(LOG_COINRANK, 'Refreshing list') | ||
const abortable = queryLoop(1) | ||
abort = abortable.abort | ||
abortable.promise | ||
const abortableQueryLoop = queryLoop(1) | ||
loopAbort = abortableQueryLoop.abort | ||
abortableQueryLoop.promise | ||
.catch(e => console.error(`Error in query loop: ${e.message}`)) | ||
.finally(() => { | ||
timeoutId = setTimeout(loopBody, LISTINGS_REFRESH_INTERVAL) | ||
}) | ||
} | ||
|
||
return () => { | ||
// Reset related query state when this effect is unmounted: | ||
clearTimeout(timeoutId) | ||
abort() | ||
pageQueryAbortRef.current() | ||
coinRanking.coinRankingDatas = [] | ||
lastStartIndex.current = 1 | ||
setDataSize(0) | ||
setRequestDataSize(QUERY_PAGE_SIZE) | ||
// Reset related query state when this effect is unmounted (coingeckoFiat | ||
// changed), but the scene is still mounted: | ||
if (coingeckoFiat !== lastSceneFiat) { | ||
debugLog(LOG_COINRANK, `Clearing coinRankingDatas cache for new fiat: ${coingeckoFiat}`) | ||
clearTimeout(timeoutId) | ||
loopAbort() | ||
pageQueryAbortRef.current() | ||
coinRanking.coinRankingDatas = [] | ||
lastStartIndex.current = 1 | ||
setDataSize(0) | ||
setRequestDataSize(QUERY_PAGE_SIZE) | ||
} | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
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. Why was this 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. This was the cause of the actual bug in the task - this cleanup fn is appropriate only for when the scene is still mounted, implying that the fiat changed while the scene was mounted, so clearing everything was appropriate. It's inappropriate to run this cleanup when the scene unmounts, which was happening in the original implementation. We lose the cache in that case, which defeats the purpose of the cache. 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. What's the reason for the |
||
}, [coingeckoFiat /* reset subscription on fiat change */, queryLoop]) | ||
|
||
const listdata: number[] = React.useMemo(() => { | ||
const listData: number[] = React.useMemo(() => { | ||
debugLog(LOG_COINRANK, `Updating listdata dataSize=${dataSize} searchText=${searchText}`) | ||
const out = [] | ||
for (let i = 0; i < dataSize; i++) { | ||
|
@@ -282,17 +306,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> | ||
</> | ||
)} | ||
|
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.
Why not put this in a
useUnmount
hook?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.
See comment
#5544 (comment)
We need to pass mounted.current around separately anyway, so might as well fold
the changes into this existing effect.
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.
Why I suggest
useUnmount
is to avoid the disabled eslint rule hack.