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

Make lists with polling and pagination more stable #1534

Merged
merged 4 commits into from
Oct 7, 2024
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/1534.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make lists with polling and pagination more stable
67 changes: 67 additions & 0 deletions src/app/hooks/useListBeforeDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useEffect, useState } from 'react'
import { Layer, useGetRuntimeStatus, useGetStatus } from '../../oasis-nexus/api'
import { AppError, AppErrors } from 'types/errors'
import { SearchScope } from 'types/searchScope'

// Workaround around "before" filter exclusive maximum transaction time
function addOneSecond(timestamp: string | undefined) {
if (!timestamp) {
return undefined
}
const date = new Date(timestamp)
date.setSeconds(date.getSeconds() + 1)
return date.toISOString()
}

const useListBeforeDate = (
latestBlockTime: string | undefined,
offset: number,
setOffsetAssociatedWithDate: (offset: number) => void,
) => {
const [beforeDate, setBeforeDate] = useState<string | undefined>(undefined)
const setBeforeDateFromCollection = (newDate: string | undefined) => {
const adjustedDate = addOneSecond(newDate)
// Prevents infinite loop re-renders
if (offset === 0 && beforeDate !== adjustedDate) {
buberdds marked this conversation as resolved.
Show resolved Hide resolved
setBeforeDate(adjustedDate)
}
}

useEffect(() => {
if (!beforeDate) {
// When view is init on page other than 1, we don't know the first tx timestamp in collection.
// We rely on status endpoint "latest_block_time" prop
setBeforeDate(addOneSecond(latestBlockTime))
setOffsetAssociatedWithDate(offset)
}
}, [latestBlockTime, beforeDate, offset, setOffsetAssociatedWithDate])

return { beforeDate, setBeforeDateFromCollection }
}

export const useRuntimeListBeforeDate = (scope: SearchScope, offset: number) => {
const [offsetAssociatedWithDate, setOffsetAssociatedWithDate] = useState<number | undefined>(offset)

if (scope.layer === Layer.consensus) {
throw new AppError(AppErrors.UnsupportedLayer)
}

const { data } = useGetRuntimeStatus(scope.network, scope.layer, {
query: {
queryKey: ['runtimeStatus', scope.network, scope.layer, offsetAssociatedWithDate],
},
})

return useListBeforeDate(data?.data.latest_block_time, offset, setOffsetAssociatedWithDate)
}

export const useConsensusListBeforeDate = (scope: SearchScope, offset: number) => {
const [offsetAssociatedWithDate, setOffsetAssociatedWithDate] = useState<number | undefined>(offset)
const { data } = useGetStatus(scope.network, {
query: {
queryKey: ['consensusStatus', scope.network, offsetAssociatedWithDate],
},
})

return useListBeforeDate(data?.data.latest_block_time, offset, setOffsetAssociatedWithDate)
}
40 changes: 24 additions & 16 deletions src/app/pages/ConsensusBlocksPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { TableLayout, TableLayoutButton } from '../../components/TableLayoutButt
import { LoadMoreButton } from '../../components/LoadMoreButton'
import { useRequiredScopeParam } from '../../hooks/useScopeParam'
import { ConsensusBlocks, TableConsensusBlockList } from '../../components/Blocks/ConsensusBlocks'
import { useConsensusListBeforeDate } from '../../hooks/useListBeforeDate'

const PAGE_SIZE = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE

Expand All @@ -35,6 +36,8 @@ export const ConsensusBlocksPage: FC = () => {
const pagination = useSearchParamsPagination('page')
const offset = (pagination.selectedPage - 1) * PAGE_SIZE
const scope = useRequiredScopeParam()
const enablePolling = offset === 0
const { beforeDate, setBeforeDateFromCollection } = useConsensusListBeforeDate(scope, offset)

useEffect(() => {
if (!isMobile) {
Expand All @@ -47,25 +50,29 @@ export const ConsensusBlocksPage: FC = () => {
{
limit: tableView === TableLayout.Vertical ? offset + PAGE_SIZE : PAGE_SIZE,
offset: tableView === TableLayout.Vertical ? 0 : offset,
before: enablePolling ? undefined : beforeDate,
buberdds marked this conversation as resolved.
Show resolved Hide resolved
},
{
query: {
refetchInterval: REFETCH_INTERVAL,
structuralSharing: (previousState, nextState) => {
const oldBlockIds = new Set(previousState?.data.blocks.map(block => block.height))
return {
...nextState,
data: {
...nextState.data,
blocks: nextState.data.blocks.map(block => {
return {
...block,
markAsNew: previousState ? !oldBlockIds.has(block.height) : false,
}
}),
},
}
},
enabled: enablePolling || !!beforeDate,
refetchInterval: enablePolling ? REFETCH_INTERVAL : undefined,
structuralSharing: enablePolling
? (previousState, nextState) => {
const oldBlockIds = new Set(previousState?.data.blocks.map(block => block.height))
return {
...nextState,
data: {
...nextState.data,
blocks: nextState.data.blocks.map(block => {
return {
...block,
markAsNew: previousState ? !oldBlockIds.has(block.height) : false,
}
}),
},
}
}
: undefined,
// Keep showing data while loading more
keepPreviousData: tableView === TableLayout.Vertical,
},
Expand All @@ -74,6 +81,7 @@ export const ConsensusBlocksPage: FC = () => {

const { isLoading, isFetched, data } = blocksQuery
const blocks = data?.data.blocks
setBeforeDateFromCollection(data?.data.blocks[0].timestamp)

if (isFetched && offset && !blocks?.length) {
throw AppErrors.PageDoesNotExist
Expand Down
40 changes: 24 additions & 16 deletions src/app/pages/ConsensusTransactionsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TableLayout, TableLayoutButton } from '../../components/TableLayoutButt
import { useRequiredScopeParam } from '../../hooks/useScopeParam'
import { VerticalList } from '../../components/VerticalList'
import { ConsensusTransactionDetailView } from '../ConsensusTransactionDetailPage'
import { useConsensusListBeforeDate } from '../../hooks/useListBeforeDate'

export const ConsensusTransactionsPage: FC = () => {
const [tableView, setTableView] = useState<TableLayout>(TableLayout.Horizontal)
Expand All @@ -23,6 +24,8 @@ export const ConsensusTransactionsPage: FC = () => {
const pagination = useSearchParamsPagination('page')
const offset = (pagination.selectedPage - 1) * limit
const scope = useRequiredScopeParam()
const enablePolling = offset === 0
const { beforeDate, setBeforeDateFromCollection } = useConsensusListBeforeDate(scope, offset)

useEffect(() => {
if (!isMobile) {
Expand All @@ -35,25 +38,29 @@ export const ConsensusTransactionsPage: FC = () => {
{
limit: tableView === TableLayout.Vertical ? offset + limit : limit,
offset: tableView === TableLayout.Vertical ? 0 : offset,
before: enablePolling ? undefined : beforeDate,
},
{
query: {
refetchInterval: REFETCH_INTERVAL,
structuralSharing: (previousState, nextState) => {
const oldTxHashes = new Set(previousState?.data.transactions.map(tx => tx.hash))
return {
...nextState,
data: {
...nextState.data,
transactions: nextState.data.transactions.map(tx => {
return {
...tx,
markAsNew: previousState ? !oldTxHashes.has(tx.hash) : false,
}
}),
},
}
},
enabled: enablePolling || !!beforeDate,
refetchInterval: enablePolling ? REFETCH_INTERVAL : undefined,
structuralSharing: enablePolling
? (previousState, nextState) => {
const oldTxHashes = new Set(previousState?.data.transactions.map(tx => tx.hash))
return {
...nextState,
data: {
...nextState.data,
transactions: nextState.data.transactions.map(tx => {
return {
...tx,
markAsNew: previousState ? !oldTxHashes.has(tx.hash) : false,
}
}),
},
}
}
: undefined,
keepPreviousData: tableView === TableLayout.Vertical,
},
},
Expand All @@ -62,6 +69,7 @@ export const ConsensusTransactionsPage: FC = () => {
const { isLoading, isFetched, data } = transactionsQuery

const transactions = data?.data.transactions
setBeforeDateFromCollection(data?.data.transactions[0].timestamp)

if (isFetched && pagination.selectedPage > 1 && !transactions?.length) {
throw AppErrors.PageDoesNotExist
Expand Down
40 changes: 24 additions & 16 deletions src/app/pages/RuntimeBlocksPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TableLayout, TableLayoutButton } from '../../components/TableLayoutButt
import { LoadMoreButton } from '../../components/LoadMoreButton'
import { useRequiredScopeParam } from '../../hooks/useScopeParam'
import { VerticalList } from '../../components/VerticalList'
import { useRuntimeListBeforeDate } from '../../hooks/useListBeforeDate'

const PAGE_SIZE = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE

Expand All @@ -25,6 +26,8 @@ export const RuntimeBlocksPage: FC = () => {
const pagination = useSearchParamsPagination('page')
const offset = (pagination.selectedPage - 1) * PAGE_SIZE
const scope = useRequiredScopeParam()
const enablePolling = offset === 0
const { beforeDate, setBeforeDateFromCollection } = useRuntimeListBeforeDate(scope, offset)
// Consensus is not yet enabled in ENABLED_LAYERS, just some preparation
if (scope.layer === Layer.consensus) {
throw AppErrors.UnsupportedLayer
Expand All @@ -44,25 +47,29 @@ export const RuntimeBlocksPage: FC = () => {
{
limit: tableView === TableLayout.Vertical ? offset + PAGE_SIZE : PAGE_SIZE,
offset: tableView === TableLayout.Vertical ? 0 : offset,
before: enablePolling ? undefined : beforeDate,
},
{
query: {
refetchInterval: REFETCH_INTERVAL,
structuralSharing: (previousState, nextState) => {
const oldBlockIds = new Set(previousState?.data.blocks.map(block => block.round))
return {
...nextState,
data: {
...nextState.data,
blocks: nextState.data.blocks.map(block => {
return {
...block,
markAsNew: previousState ? !oldBlockIds.has(block.round) : false,
}
}),
},
}
},
enabled: enablePolling || !!beforeDate,
refetchInterval: enablePolling ? REFETCH_INTERVAL : undefined,
structuralSharing: enablePolling
? (previousState, nextState) => {
const oldBlockIds = new Set(previousState?.data.blocks.map(block => block.round))
return {
...nextState,
data: {
...nextState.data,
blocks: nextState.data.blocks.map(block => {
return {
...block,
markAsNew: previousState ? !oldBlockIds.has(block.round) : false,
}
}),
},
}
}
: undefined,
// Keep showing data while loading more
keepPreviousData: tableView === TableLayout.Vertical,
},
Expand All @@ -71,6 +78,7 @@ export const RuntimeBlocksPage: FC = () => {

const { isLoading, isFetched, data } = blocksQuery
const blocks = data?.data.blocks
setBeforeDateFromCollection(data?.data.blocks[0].timestamp)

if (isFetched && offset && !blocks?.length) {
throw AppErrors.PageDoesNotExist
Expand Down
41 changes: 24 additions & 17 deletions src/app/pages/RuntimeTransactionsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useRequiredScopeParam } from '../../hooks/useScopeParam'
import { useAllTokenPrices } from '../../../coin-gecko/api'
import { VerticalList } from '../../components/VerticalList'
import { getFiatCurrencyForScope } from '../../../config'
import { useRuntimeListBeforeDate } from '../../hooks/useListBeforeDate'

const limit = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE

Expand All @@ -27,7 +28,8 @@ export const RuntimeTransactionsPage: FC = () => {
const pagination = useSearchParamsPagination('page')
const offset = (pagination.selectedPage - 1) * limit
const scope = useRequiredScopeParam()

const enablePolling = offset === 0
const { beforeDate, setBeforeDateFromCollection } = useRuntimeListBeforeDate(scope, offset)
// Consensus is not yet enabled in ENABLED_LAYERS, just some preparation
if (scope.layer === Layer.consensus) {
throw AppErrors.UnsupportedLayer
Expand All @@ -49,25 +51,29 @@ export const RuntimeTransactionsPage: FC = () => {
{
limit: tableView === TableLayout.Vertical ? offset + limit : limit,
offset: tableView === TableLayout.Vertical ? 0 : offset,
before: enablePolling ? undefined : beforeDate,
},
{
query: {
refetchInterval: REFETCH_INTERVAL,
structuralSharing: (previousState, nextState) => {
const oldTxHashes = new Set(previousState?.data.transactions.map(tx => tx.hash))
return {
...nextState,
data: {
...nextState.data,
transactions: nextState.data.transactions.map(tx => {
return {
...tx,
markAsNew: previousState ? !oldTxHashes.has(tx.hash) : false,
}
}),
},
}
},
enabled: enablePolling || !!beforeDate,
refetchInterval: enablePolling ? REFETCH_INTERVAL : undefined,
structuralSharing: enablePolling
? (previousState, nextState) => {
const oldTxHashes = new Set(previousState?.data.transactions.map(tx => tx.hash))
return {
...nextState,
data: {
...nextState.data,
transactions: nextState.data.transactions.map(tx => {
return {
...tx,
markAsNew: previousState ? !oldTxHashes.has(tx.hash) : false,
}
}),
},
}
}
: undefined,
// Keep showing data while loading more
keepPreviousData: tableView === TableLayout.Vertical,
},
Expand All @@ -77,6 +83,7 @@ export const RuntimeTransactionsPage: FC = () => {
const { isLoading, isFetched, data } = transactionsQuery

const transactions = data?.data.transactions
setBeforeDateFromCollection(data?.data.transactions[0].timestamp)

if (isFetched && pagination.selectedPage > 1 && !transactions?.length) {
throw AppErrors.PageDoesNotExist
Expand Down
Loading