Skip to content
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

feat(truesight): add offset when request CoinGecko chart data #1186

Merged
merged 1 commit into from
Aug 2, 2022
Merged
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
60 changes: 34 additions & 26 deletions src/pages/TrueSight/hooks/useGetCoinGeckoChartData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/camelcase */
import { useEffect, useMemo, useState } from 'react'
import { useMemo, useRef } from 'react'
import { TrueSightTimeframe } from 'pages/TrueSight/index'
import { NETWORKS_INFO, TRUESIGHT_NETWORK_TO_CHAINID } from 'constants/networks'
import useSWRImmutable from 'swr/immutable'

export interface CoinGeckoChartData {
prices: [number, number][]
Expand All @@ -15,6 +16,8 @@ export interface FormattedCoinGeckoChartData {
totalVolumes: { time: number; value: string }[]
}

const initialCoinGeckoChartData = { prices: [], market_caps: [], total_volumes: [] }

function formatCoinGeckoChartData(data: CoinGeckoChartData): FormattedCoinGeckoChartData {
return {
prices: data.prices.map(item => ({ time: item[0] ?? 0, value: item[1].toString() ?? 0 })),
Expand All @@ -23,20 +26,24 @@ function formatCoinGeckoChartData(data: CoinGeckoChartData): FormattedCoinGeckoC
}
}

const FETCHING_COINGECKO_CHART_DATA_OFFSET = 2000
const FETCHING_COINGECKO_CHART_DATA_ERROR_RETRY_INTERVAL = FETCHING_COINGECKO_CHART_DATA_OFFSET / 2

export default function useGetCoinGeckoChartData(
tokenNetwork: string | undefined,
tokenAddress: string | undefined,
timeframe: TrueSightTimeframe,
) {
const [data, setData] = useState<CoinGeckoChartData>({ prices: [], market_caps: [], total_volumes: [] })
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error>()
const latestRequestingTime = useRef(0)
const controller = useRef(new AbortController())

useEffect(() => {
const controller = new AbortController()
const fetchData = async () => {
const { data, isValidating: isLoading, error } = useSWRImmutable<CoinGeckoChartData>(
['useGetCoinGeckoChartData', timeframe, tokenAddress ?? '', tokenNetwork ?? ''],
async () => {
if (tokenNetwork && tokenAddress) {
try {
controller.current.abort()
controller.current = new AbortController()
const to = Math.floor(Date.now() / 1000)
const from = to - (timeframe === TrueSightTimeframe.ONE_DAY ? 24 * 3600 : 24 * 3600 * 7)
const chainId = TRUESIGHT_NETWORK_TO_CHAINID[tokenNetwork]
Expand All @@ -51,32 +58,33 @@ export default function useGetCoinGeckoChartData(
// CAKE
url = `https://api.coingecko.com/api/v3/coins/binance-smart-chain/contract/0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82/market_chart/range?vs_currency=usd&from=1649824248&to=1649910648`
}
setError(undefined)
setIsLoading(true)
setData({ prices: [], market_caps: [], total_volumes: [] })
const response = await fetch(url, { signal: controller.signal })
if (Date.now() - latestRequestingTime.current < FETCHING_COINGECKO_CHART_DATA_OFFSET) {
// Too Many Request
throw new Error('429')
Copy link
Member

Choose a reason for hiding this comment

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

should create a particular error like ToManyRequestError. the other components will check it to handle it's logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is my trick only, no need for a variable or constant here. If we have one more use-case like this CoinGecko, I'll refactor <3.

Copy link
Member

Choose a reason for hiding this comment

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

Sound's good!

}
latestRequestingTime.current = Date.now()
const response = await fetch(url, { signal: controller.current.signal })
if (response.ok) {
const result = await response.json()
setData(result)
return result
}
setIsLoading(false)
} catch (err) {
console.error(err)
setError(err)
setIsLoading(false)
// Too Many Request
throw new Error('429')
}
}
}

fetchData()

return () => {
controller.abort()
}
}, [timeframe, tokenAddress, tokenNetwork])
return initialCoinGeckoChartData
},
{
errorRetryInterval: FETCHING_COINGECKO_CHART_DATA_ERROR_RETRY_INTERVAL,
errorRetryCount: 60 / FETCHING_COINGECKO_CHART_DATA_ERROR_RETRY_INTERVAL, // CoinGecko might block upto 1 minute
},
)

return useMemo(() => {
const formattedData = formatCoinGeckoChartData(data)
return { isLoading, data: formattedData, error }
const formattedData = formatCoinGeckoChartData(data ?? initialCoinGeckoChartData)

// If the error is Too Many Request, show loading and then retry in intervals until success
return { isLoading: error?.message === '429' ? true : isLoading, data: formattedData }
}, [data, isLoading, error])
}