Skip to content

Commit

Permalink
feat: market-data slice improvements (#7615)
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre authored Aug 27, 2024
1 parent b888cf6 commit d72c8d4
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const CustomAssetAcknowledgement: React.FC<CustomAssetAcknowledgementProp
// Add asset to the store
dispatch(assetsSlice.actions.upsertAsset(asset))
// Use the market API to get the market data for the custom asset
dispatch(marketApi.endpoints.findByAssetIds.initiate([asset.assetId]))
dispatch(marketApi.endpoints.findByAssetId.initiate(asset.assetId))
// Once the custom asset is in the store, proceed as if it was a normal asset
handleAssetClick(asset)
}, [dispatch, handleAssetClick, asset])
Expand Down
41 changes: 29 additions & 12 deletions src/components/TransactionHistoryRows/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Dex, TransferType } from '@shapeshiftoss/unchained-client'
import dayjs from 'dayjs'
import { useMemo, useState } from 'react'
import type { TxDetails } from 'hooks/useTxDetails/useTxDetails'
import { useFindPriceHistoryByAssetIdsQuery } from 'state/slices/marketDataSlice/marketDataSlice'
import { useFindPriceHistoryByAssetIdQuery } from 'state/slices/marketDataSlice/marketDataSlice'
import type { FindPriceHistoryByAssetIdArgs } from 'state/slices/marketDataSlice/types'
import { selectCryptoPriceHistoryTimeframe } from 'state/slices/selectors'
import { useAppSelector } from 'state/store'
Expand All @@ -17,11 +17,20 @@ export const useTradeFees = ({ txDetails }: { txDetails: TxDetails }) => {
selectCryptoPriceHistoryTimeframe(state, HistoryTimeframe.ALL),
)

const [priceHistoryParams, setPriceHistoryParams] = useState<
const [sellAssetPriceHistoryParams, setSellAssetPriceHistoryParams] = useState<
FindPriceHistoryByAssetIdArgs | typeof skipToken
>(skipToken)

const { isLoading } = useFindPriceHistoryByAssetIdsQuery(priceHistoryParams)
const [buyAssetPriceHistoryParams, setBuyAssetPriceHistoryParams] = useState<
FindPriceHistoryByAssetIdArgs | typeof skipToken
>(skipToken)

const { isLoading: isSellAssetPriceHistoryLoading } = useFindPriceHistoryByAssetIdQuery(
sellAssetPriceHistoryParams,
)
const { isLoading: isBuyAssetPriceHistoryLoading } = useFindPriceHistoryByAssetIdQuery(
buyAssetPriceHistoryParams,
)

const buy = useMemo(
() => txDetails.transfers.find(transfer => transfer.type === TransferType.Receive),
Expand All @@ -37,16 +46,16 @@ export const useTradeFees = ({ txDetails }: { txDetails: TxDetails }) => {
if (!(txDetails.tx.trade && buy && sell)) return
if (txDetails.tx.trade.dexName !== Dex.CowSwap) return

const assetIds = []
if (!cryptoPriceHistoryData?.[buy.asset.assetId]) assetIds.push(buy.asset.assetId)
if (!cryptoPriceHistoryData?.[sell.asset.assetId]) assetIds.push(sell.asset.assetId)

if (assetIds.length > 0 && !isLoading) {
setPriceHistoryParams({
assetIds,
if (!cryptoPriceHistoryData?.[buy.asset.assetId] && !isBuyAssetPriceHistoryLoading)
setBuyAssetPriceHistoryParams({
assetId: buy.asset.assetId,
timeframe: HistoryTimeframe.ALL,
})
if (!cryptoPriceHistoryData?.[sell.asset.assetId] && !isSellAssetPriceHistoryLoading)
setSellAssetPriceHistoryParams({
assetId: sell.asset.assetId,
timeframe: HistoryTimeframe.ALL,
})
}

const tradeFees = getTradeFees({
sell,
Expand All @@ -56,7 +65,15 @@ export const useTradeFees = ({ txDetails }: { txDetails: TxDetails }) => {
})

return tradeFees
}, [txDetails.tx.trade, txDetails.tx.blockTime, buy, sell, cryptoPriceHistoryData, isLoading])
}, [
txDetails.tx.trade,
txDetails.tx.blockTime,
buy,
sell,
cryptoPriceHistoryData,
isBuyAssetPriceHistoryLoading,
isSellAssetPriceHistoryLoading,
])

return tradeFees
}
54 changes: 28 additions & 26 deletions src/context/AppProvider/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { usePrevious, useToast } from '@chakra-ui/react'
import { fromAccountId } from '@shapeshiftoss/caip'
import { MetaMaskShapeShiftMultiChainHDWallet } from '@shapeshiftoss/hdwallet-shapeshift-multichain'
import type { AccountMetadataById } from '@shapeshiftoss/types'
import { useQuery } from '@tanstack/react-query'
import { useQueries } from '@tanstack/react-query'
import { DEFAULT_HISTORY_TIMEFRAME } from 'constants/Config'
import { LanguageTypeEnum } from 'constants/LanguageTypeEnum'
import React, { useEffect } from 'react'
Expand Down Expand Up @@ -243,31 +243,33 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
}, [dispatch, requestedAccountIds, portfolioLoadingStatus])

const marketDataPollingInterval = 60 * 15 * 1000 // refetch data every 15 minutes
useQuery({
queryKey: ['marketData', {}],
queryFn: async () => {
await dispatch(
marketApi.endpoints.findByAssetIds.initiate(portfolioAssetIds, {
// Since we use react-query as a polling wrapper, every initiate call *is* a force refetch here
forceRefetch: true,
}),
)
useQueries({
queries: portfolioAssetIds.map(assetId => ({
queryKey: ['marketData', assetId],
queryFn: async () => {
await dispatch(
marketApi.endpoints.findByAssetId.initiate(assetId, {
// Since we use react-query as a polling wrapper, every initiate call *is* a force refetch here
forceRefetch: true,
}),
)

// used to trigger mixpanel init after load of market data
dispatch(marketData.actions.setMarketDataLoaded())

// We *have* to return a value other than undefined from react-query queries, see
// https://tanstack.com/query/v4/docs/react/guides/migrating-to-react-query-4#undefined-is-an-illegal-cache-value-for-successful-queries
return null
},
// once the portfolio is loaded, fetch market data for all portfolio assets
// and start refetch timer to keep market data up to date
enabled: portfolioLoadingStatus !== 'loading',
refetchInterval: marketDataPollingInterval,
// Do NOT refetch market data in background to avoid spamming coingecko
refetchIntervalInBackground: false,
// Do NOT refetch market data on window focus to avoid spamming coingecko
refetchOnWindowFocus: false,
// used to trigger mixpanel init after load of market data
dispatch(marketData.actions.setMarketDataLoaded())

// We *have* to return a value other than undefined from react-query queries, see
// https://tanstack.com/query/v4/docs/react/guides/migrating-to-react-query-4#undefined-is-an-illegal-cache-value-for-successful-queries
return null
},
// once the portfolio is loaded, fetch market data for all portfolio assets
// and start refetch timer to keep market data up to date
enabled: portfolioLoadingStatus !== 'loading',
refetchInterval: marketDataPollingInterval,
// Do NOT refetch market data in background to avoid spamming coingecko
refetchIntervalInBackground: false,
// Do NOT refetch market data on window focus to avoid spamming coingecko
refetchOnWindowFocus: false,
})),
})

/**
Expand Down Expand Up @@ -308,7 +310,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
useEffect(() => {
// early return for routes that don't contain an assetId, no need to refetch marketData granularly
if (!routeAssetId) return
dispatch(marketApi.endpoints.findByAssetIds.initiate([routeAssetId]))
dispatch(marketApi.endpoints.findByAssetId.initiate(routeAssetId))
}, [dispatch, routeAssetId])

// If the assets aren't loaded, then the app isn't ready to render
Expand Down
76 changes: 41 additions & 35 deletions src/hooks/useFetchPriceHistories/useFetchPriceHistories.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { AssetId } from '@shapeshiftoss/caip'
import type { HistoryTimeframe } from '@shapeshiftoss/types'
import { useQuery } from '@tanstack/react-query'
import { useQueries } from '@tanstack/react-query'
import { DEFAULT_HISTORY_TIMEFRAME } from 'constants/Config'
import { useEffect } from 'react'
import { marketApi, marketData } from 'state/slices/marketDataSlice/marketDataSlice'
import { selectPortfolioLoadingStatus, selectSelectedCurrency } from 'state/slices/selectors'
import { useAppDispatch, useAppSelector } from 'state/store'

const { findPriceHistoryByAssetIds, findPriceHistoryByFiatSymbol } = marketApi.endpoints
const { findPriceHistoryByFiatSymbol } = marketApi.endpoints

const marketDataPollingInterval = 60 * 15 * 1000 // refetch data every 15 minutes

Expand All @@ -16,47 +16,53 @@ export const useFetchPriceHistories = (assetIds: AssetId[], timeframe: HistoryTi
const symbol = useAppSelector(selectSelectedCurrency)

useEffect(() => {
dispatch(findPriceHistoryByAssetIds.initiate({ assetIds, timeframe }))
assetIds.forEach(assetId => {
dispatch(marketApi.endpoints.findPriceHistoryByAssetId.initiate({ assetId, timeframe }))
})
}, [assetIds, dispatch, timeframe])

const portfolioLoadingStatus = useAppSelector(selectPortfolioLoadingStatus)

useQuery({
queryKey: ['marketData', assetIds],
queryFn: async () => {
// Only commented out to make it clear we do NOT want to use RTK options here
// We use react-query as a wrapper because it allows us to disable refetch for background tabs
// const opts = { subscriptionOptions: { pollingInterval: marketDataPollingInterval } }
const timeframe = DEFAULT_HISTORY_TIMEFRAME
useQueries({
queries: assetIds.map(assetId => ({
queryKey: ['marketData', assetId],
queryFn: async () => {
// Only commented out to make it clear we do NOT want to use RTK options here
// We use react-query as a wrapper because it allows us to disable refetch for background tabs
// const opts = { subscriptionOptions: { pollingInterval: marketDataPollingInterval } }
const timeframe = DEFAULT_HISTORY_TIMEFRAME

await Promise.all([
dispatch(
marketApi.endpoints.findPriceHistoryByAssetIds.initiate(
{
timeframe,
assetIds,
},
// Since we use react-query as a polling wrapper, every initiate call *is* a force refetch here
{ forceRefetch: true },
await Promise.all(
assetIds.map(assetId =>
dispatch(
marketApi.endpoints.findPriceHistoryByAssetId.initiate(
{
timeframe,
assetId,
},
// Since we use react-query as a polling wrapper, every initiate call *is* a force refetch here
{ forceRefetch: true },
),
),
),
),
])
)

// used to trigger mixpanel init after load of market data
dispatch(marketData.actions.setMarketDataLoaded())
// used to trigger mixpanel init after load of market data
dispatch(marketData.actions.setMarketDataLoaded())

// We *have* to return a value other than undefined from react-query queries, see
// https://tanstack.com/query/v4/docs/react/guides/migrating-to-react-query-4#undefined-is-an-illegal-cache-value-for-successful-queries
return null
},
// once the portfolio is loaded, fetch market data for all portfolio assets
// and start refetch timer to keep market data up to date
enabled: portfolioLoadingStatus !== 'loading',
refetchInterval: marketDataPollingInterval,
// Do NOT refetch market data in background to avoid spamming coingecko
refetchIntervalInBackground: false,
// Do NOT refetch market data on window focus to avoid spamming coingecko
refetchOnWindowFocus: false,
// We *have* to return a value other than undefined from react-query queries, see
// https://tanstack.com/query/v4/docs/react/guides/migrating-to-react-query-4#undefined-is-an-illegal-cache-value-for-successful-queries
return null
},
// once the portfolio is loaded, fetch market data for all portfolio assets
// and start refetch timer to keep market data up to date
enabled: portfolioLoadingStatus !== 'loading',
refetchInterval: marketDataPollingInterval,
// Do NOT refetch market data in background to avoid spamming coingecko
refetchIntervalInBackground: false,
// Do NOT refetch market data on window focus to avoid spamming coingecko
refetchOnWindowFocus: false,
})),
})

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/RFOX/components/Stake/StakeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const StakeInput: React.FC<StakeInputProps & StakeRouteProps> = ({

useEffect(() => {
// hydrate FOX.ARB market data in case the user doesn't hold it
dispatch(marketApi.endpoints.findByAssetIds.initiate([stakingAssetId]))
dispatch(marketApi.endpoints.findByAssetId.initiate(stakingAssetId))
}, [dispatch, selectedAssetId, stakingAssetId])
useEffect(() => {
// Only set this once, never collapse out
Expand Down
6 changes: 4 additions & 2 deletions src/state/apis/fiatRamps/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export const useFetchFiatAssetMarketData = (assetIds: AssetId[]): void => {
const timeframe = HistoryTimeframe.DAY

if (assetIds.length > 0) {
dispatch(marketApi.endpoints.findPriceHistoryByAssetIds.initiate({ assetIds, timeframe }))
dispatch(marketApi.endpoints.findByAssetIds.initiate(assetIds))
assetIds.forEach(assetId => {
dispatch(marketApi.endpoints.findPriceHistoryByAssetId.initiate({ assetId, timeframe }))
dispatch(marketApi.endpoints.findByAssetId.initiate(assetId))
})
}
}, [assetIds, dispatch])
}
7 changes: 4 additions & 3 deletions src/state/apis/swapper/swapperApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ export const swapperApi = createApi({
if (!isSwapperEnabled) return { data: {} }

// hydrate crypto market data for buy and sell assets
await dispatch(
marketApi.endpoints.findByAssetIds.initiate([sellAsset.assetId, buyAsset.assetId]),
)
await Promise.all([
dispatch(marketApi.endpoints.findByAssetId.initiate(sellAsset.assetId)),
dispatch(marketApi.endpoints.findByAssetId.initiate(buyAsset.assetId)),
])

const swapperDeps: SwapperDeps = {
assetsById: selectAssets(state),
Expand Down
Loading

0 comments on commit d72c8d4

Please sign in to comment.