Skip to content

Commit

Permalink
Merge pull request #1534 from oasisprotocol/mz/useBeforeDate
Browse files Browse the repository at this point in the history
Make lists with polling and pagination more stable
  • Loading branch information
buberdds authored Oct 7, 2024
2 parents 0ee29e1 + 84ab80b commit 0324c81
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 65 deletions.
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) {
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,
},
{
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

0 comments on commit 0324c81

Please sign in to comment.