Commit 28538214 authored by lynn's avatar lynn Committed by GitHub

feat: new lazy load that scrolls whole window and not inside fixed size container (#4684)

* init

* messy but working omfg

* dont set initial to 500 set to just 1 for testing purposes

* it looks pretty now and works well

* sorting filtering and suspense loading are working

* fix comments

* handle token rows lacking addresS

* start working with new data schema

* new gql schema

* initial commit

* improved performance, added filtering

* lint

* removed comments and accidental settings.json changes

* refactor: switch explore over to new queries (#4657)

* initial commit
* improved performance, added filtering
* addressed pr comments
* fixed typescript issue

* merges

* fix

* fix oopsies

* fix accidental changes

* its working

* drop leftover comment

* clean up loaded row props

* respond to comments

* respond to jordan comments

* init

* remove unnecessary pkgs

* undo yarn lock changes

* loading rows fix

* change loading rows to 3 as per fred instruction

* remove anys
Co-authored-by: default avatarJordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: default avatarcartcrom <cartergcromer@gmail.com>
Co-authored-by: default avatarcartcrom <39385577+cartcrom@users.noreply.github.com>
parent 4f48f337
......@@ -10,7 +10,8 @@ import { TokenSortMethod, TopToken } from 'graphql/data/TopTokens'
import { TimePeriod } from 'graphql/data/util'
import { useCurrency } from 'hooks/Tokens'
import { useAtomValue } from 'jotai/utils'
import { ReactNode } from 'react'
import { ForwardedRef, forwardRef } from 'react'
import { CSSProperties, HTMLProps, ReactHTMLElement, ReactNode } from 'react'
import { ArrowDown, ArrowUp, Heart } from 'react-feather'
import { Link } from 'react-router-dom'
import styled, { css, useTheme } from 'styled-components/macro'
......@@ -37,6 +38,8 @@ import {
import { formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart'
import { DISPLAYS } from './TimeSelector'
export const MAX_TOKENS_TO_LOAD = 100
const Cell = styled.div`
display: flex;
align-items: center;
......@@ -401,6 +404,7 @@ export function TokenRow({
tokenInfo: ReactNode
volume: ReactNode
last?: boolean
style?: CSSProperties
}) {
const favoriteTokensEnabled = useFavoriteTokensFlag() === FavoriteTokensVariant.Enabled
const rowCells = (
......@@ -463,14 +467,15 @@ export function LoadingRow() {
)
}
interface LoadedRowProps {
interface LoadedRowProps extends HTMLProps<ReactHTMLElement<HTMLElement>> {
tokenListIndex: number
tokenListLength: number
token: TopToken
}
/* Loaded State: row component with token information */
export default function LoadedRow({ tokenListIndex, tokenListLength, token }: LoadedRowProps) {
export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HTMLDivElement>) => {
const { tokenListIndex, tokenListLength, token } = props
const tokenAddress = token?.address
const currency = useCurrency(tokenAddress)
const tokenName = token?.name
......@@ -498,6 +503,7 @@ export default function LoadedRow({ tokenListIndex, tokenListLength, token }: Lo
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
return (
<div ref={ref}>
<StyledLink
to={`/tokens/${tokenAddress}`}
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
......@@ -514,7 +520,7 @@ export default function LoadedRow({ tokenListIndex, tokenListLength, token }: Lo
<FavoriteIcon isFavorited={isFavorited} />
</ClickFavorited>
}
listNumber={sortAscending ? 100 - tokenListIndex : tokenListIndex + 1}
listNumber={sortAscending ? MAX_TOKENS_TO_LOAD - tokenListIndex : tokenListIndex + 1}
tokenInfo={
<ClickableName>
<LogoContainer>
......@@ -573,5 +579,8 @@ export default function LoadedRow({ tokenListIndex, tokenListLength, token }: Lo
last={tokenListIndex === tokenListLength - 1}
/>
</StyledLink>
</div>
)
}
})
LoadedRow.displayName = 'LoadedRow'
......@@ -2,12 +2,15 @@ import { Trans } from '@lingui/macro'
import { showFavoritesAtom } from 'components/Tokens/state'
import { usePrefetchTopTokens, useTopTokens } from 'graphql/data/TopTokens'
import { useAtomValue } from 'jotai/utils'
import { ReactNode } from 'react'
import { ReactNode, useCallback, useRef } from 'react'
import { AlertTriangle } from 'react-feather'
import styled from 'styled-components/macro'
import { MAX_WIDTH_MEDIA_BREAKPOINT } from '../constants'
import LoadedRow, { HeaderRow, LoadingRow } from './TokenRow'
import { HeaderRow, LoadedRow, LoadingRow, MAX_TOKENS_TO_LOAD } from './TokenRow'
const LOADING_ROWS_COUNT = 3
const ROWS_PER_PAGE_FETCH = 20
const GridContainer = styled.div`
display: flex;
......@@ -23,6 +26,12 @@ const GridContainer = styled.div`
align-items: center;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
`
const TokenDataContainer = styled.div`
height: 100%;
width: 100%;
`
const NoTokenDisplay = styled.div`
display: flex;
justify-content: center;
......@@ -35,9 +44,6 @@ const NoTokenDisplay = styled.div`
padding: 0px 28px;
gap: 8px;
`
const TokenRowsContainer = styled.div`
width: 100%;
`
function NoTokensState({ message }: { message: ReactNode }) {
return (
......@@ -48,15 +54,14 @@ function NoTokensState({ message }: { message: ReactNode }) {
)
}
const LOADING_ROWS = Array.from({ length: 100 })
.fill(0)
.map((_item, index) => <LoadingRow key={index} />)
const LoadingMoreRows = Array(LOADING_ROWS_COUNT).fill(<LoadingRow />)
const InitialLoadingRows = Array(ROWS_PER_PAGE_FETCH).fill(<LoadingRow />)
export function LoadingTokenTable() {
return (
<GridContainer>
<HeaderRow />
<TokenRowsContainer>{LOADING_ROWS}</TokenRowsContainer>
<TokenDataContainer>{InitialLoadingRows}</TokenDataContainer>
</GridContainer>
)
}
......@@ -67,9 +72,25 @@ export default function TokenTable() {
// TODO: consider moving prefetched call into app.tsx and passing it here, use a preloaded call & updated on interval every 60s
const prefetchedTokens = usePrefetchTopTokens()
const { loading, tokens, loadMoreTokens } = useTopTokens(prefetchedTokens)
const hasMore = !tokens || tokens.length < MAX_TOKENS_TO_LOAD
const observer = useRef<IntersectionObserver>()
const lastTokenRef = useCallback(
(node: HTMLDivElement) => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
loadMoreTokens()
}
})
if (node) observer.current.observe(node)
},
[loading, hasMore, loadMoreTokens]
)
/* loading and error state */
if (loading) {
if (loading && (!tokens || tokens?.length === 0)) {
return <LoadingTokenTable />
} else {
if (!tokens) {
......@@ -94,13 +115,32 @@ export default function TokenTable() {
<>
<GridContainer>
<HeaderRow />
<TokenRowsContainer>
{tokens?.map((token, index) => (
<LoadedRow key={token?.name} tokenListIndex={index} tokenListLength={tokens.length} token={token} />
))}
</TokenRowsContainer>
<TokenDataContainer>
{tokens.map((token, index) => {
if (tokens.length === index + 1) {
return (
<LoadedRow
key={token?.name}
tokenListIndex={index}
tokenListLength={tokens?.length ?? 0}
token={token}
ref={lastTokenRef}
/>
)
} else {
return (
<LoadedRow
key={token?.name}
tokenListIndex={index}
tokenListLength={tokens?.length ?? 0}
token={token}
/>
)
}
})}
{loading && LoadingMoreRows}
</TokenDataContainer>
</GridContainer>
<button onClick={loadMoreTokens}>load more</button>
</>
)
}
......
......@@ -157,10 +157,12 @@ export function useTopTokens(prefetchedData: TopTokens100Query['response']) {
)
const loadMoreTokens = useCallback(() => {
setLoading(true)
const contracts = prefetchedSelectedTokens.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE).map(toContractInput)
loadTokens(contracts, (data) => {
if (data?.tokens) {
setTokens([...(tokens ?? []), ...data.tokens])
setLoading(false)
setPage(page + 1)
}
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment