Skip to content

Commit 8d503da

Browse files
authoredDec 18, 2024··
fix: handling of token page throttling (#1100)
The token page is prone to being throttled because it tries to load in order books for the "top 10 tokens". This change moves logic to useQuery and out of redux. It then makes sure the token's information is loaded in before trying to load in dex pairs. Fixes #790 ### Type of Change <!-- Please check relevant options, delete irrelevant ones. --> - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [x] Refactor (non-breaking change that only restructures code) - [x] Tests (You added tests for code that already exists, or your new feature included in this PR) - [ ] Documentation Updates - [ ] Translation Updates - [ ] Release ### TypeScript/Hooks Update - [ ] Updated files to React Hooks - [x] Updated files to TypeScript ## Before / After ### Before ![Monosnap XRPL Explorer | Something bad happened 2024-12-17 14-04-17](https://github.com/user-attachments/assets/27ea108e-59ec-4318-9da3-60ec68c3c38c) ### After ![Monosnap XRPL Explorer | rMxCKbEDwqr7 2024-12-17 14-04-24](https://github.com/user-attachments/assets/7cc2d0d3-d686-408d-a5d2-d3c1c2f8880f)
1 parent 12e04fa commit 8d503da

File tree

11 files changed

+86
-371
lines changed

11 files changed

+86
-371
lines changed
 

‎src/containers/App/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { Transaction } from '../Transactions'
3434
import { Network } from '../Network'
3535
import { Validator } from '../Validators'
3636
import { PayString } from '../PayStrings'
37-
import Token from '../Token'
37+
import { Token } from '../Token'
3838
import { NFT } from '../NFT/NFT'
3939
import { legacyRedirect } from './legacyRedirects'
4040
import { useCustomNetworks } from '../shared/hooks'

‎src/containers/Token/TokenHeader/actionTypes.js

-4
This file was deleted.

‎src/containers/Token/TokenHeader/actions.js

-43
This file was deleted.

‎src/containers/Token/TokenHeader/index.tsx

+8-52
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
import { useContext, useEffect } from 'react'
21
import { useTranslation } from 'react-i18next'
3-
import { connect } from 'react-redux'
4-
import { bindActionCreators } from 'redux'
5-
import { loadTokenState } from './actions'
6-
import { Loader } from '../../shared/components/Loader'
72
import './styles.scss'
83
import { localizeNumber, formatLargeNumber } from '../../shared/utils'
9-
import SocketContext from '../../shared/SocketContext'
104
import Currency from '../../shared/components/Currency'
115
import { Account } from '../../shared/components/Account'
126
import DomainLink from '../../shared/components/DomainLink'
137
import { TokenTableRow } from '../../shared/components/TokenTableRow'
148
import { useLanguage } from '../../shared/hooks'
159
import { LEDGER_ROUTE, TRANSACTION_ROUTE } from '../../App/routes'
1610
import { RouteLink } from '../../shared/routing'
11+
import { TokenData } from '../../../rippled/token'
1712

1813
const CURRENCY_OPTIONS = {
1914
style: 'currency',
@@ -23,44 +18,21 @@ const CURRENCY_OPTIONS = {
2318
}
2419

2520
interface TokenHeaderProps {
26-
loading: boolean
2721
accountId: string
2822
currency: string
29-
data: {
30-
balance: string
31-
reserve: number
32-
sequence: number
33-
rate: number
34-
obligations: string
35-
domain: string
36-
emailHash: string
37-
previousLedger: number
38-
previousTxn: string
39-
flags: string[]
40-
}
41-
actions: {
42-
loadTokenState: typeof loadTokenState
43-
}
23+
data: TokenData
4424
}
4525

46-
const TokenHeader = ({
47-
actions,
26+
export const TokenHeader = ({
4827
accountId,
4928
currency,
5029
data,
51-
loading,
5230
}: TokenHeaderProps) => {
5331
const language = useLanguage()
5432
const { t } = useTranslation()
55-
const rippledSocket = useContext(SocketContext)
56-
57-
useEffect(() => {
58-
actions.loadTokenState(currency, accountId, rippledSocket)
59-
}, [accountId, actions, currency, rippledSocket])
33+
const { domain, rate, emailHash, previousLedger, previousTxn } = data
6034

6135
const renderDetails = () => {
62-
const { domain, rate, emailHash, previousLedger, previousTxn } = data
63-
6436
const prevTxn = previousTxn && previousTxn.replace(/(.{20})..+/, '$1...')
6537
const abbrvEmail = emailHash && emailHash.replace(/(.{20})..+/, '$1...')
6638
return (
@@ -156,7 +128,9 @@ const TokenHeader = ({
156128
language,
157129
CURRENCY_OPTIONS,
158130
)
159-
const obligationsBalance = formatLargeNumber(Number.parseFloat(obligations))
131+
const obligationsBalance = formatLargeNumber(
132+
Number.parseFloat(obligations || '0'),
133+
)
160134

161135
return (
162136
<div className="section header-container">
@@ -201,7 +175,6 @@ const TokenHeader = ({
201175
)
202176
}
203177

204-
const { emailHash } = data
205178
return (
206179
<div className="box token-header">
207180
<div className="section box-header">
@@ -213,24 +186,7 @@ const TokenHeader = ({
213186
/>
214187
)}
215188
</div>
216-
<div className="box-content">
217-
{loading ? <Loader /> : renderHeaderContent()}
218-
</div>
189+
<div className="box-content">{renderHeaderContent()}</div>
219190
</div>
220191
)
221192
}
222-
223-
export default connect(
224-
(state: any) => ({
225-
loading: state.tokenHeader.loading,
226-
data: state.tokenHeader.data,
227-
}),
228-
(dispatch) => ({
229-
actions: bindActionCreators(
230-
{
231-
loadTokenState,
232-
},
233-
dispatch,
234-
),
235-
}),
236-
)(TokenHeader)

‎src/containers/Token/TokenHeader/reducer.js

-33
This file was deleted.

‎src/containers/Token/TokenHeader/test/actions.test.js

-108
This file was deleted.

‎src/containers/Token/TokenHeader/test/reducer.test.js

-79
This file was deleted.

‎src/containers/Token/index.tsx

+31-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { FC, PropsWithChildren, useEffect } from 'react'
1+
import { FC, PropsWithChildren, useContext, useEffect } from 'react'
22
import { useTranslation } from 'react-i18next'
3-
import { connect } from 'react-redux'
43

54
import { Helmet } from 'react-helmet-async'
6-
import TokenHeader from './TokenHeader'
5+
import { useQuery } from 'react-query'
6+
import { TokenHeader } from './TokenHeader'
77
import { TokenTransactionTable } from './TokenTransactionTable'
88
import { DEXPairs } from './DEXPairs'
99
import NoMatch from '../NoMatch'
@@ -14,6 +14,9 @@ import { useAnalytics } from '../shared/analytics'
1414
import { ErrorMessages } from '../shared/Interfaces'
1515
import { TOKEN_ROUTE } from '../App/routes'
1616
import { useRouteParams } from '../shared/routing'
17+
import { getToken } from '../../rippled'
18+
import SocketContext from '../shared/SocketContext'
19+
import { Loader } from '../shared/components/Loader'
1720

1821
const IS_MAINNET = process.env.VITE_ENVIRONMENT === 'mainnet'
1922

@@ -45,11 +48,20 @@ const Page: FC<PropsWithChildren<{ accountId: string }>> = ({
4548
</div>
4649
)
4750

48-
const Token: FC<{ error: string }> = ({ error }) => {
51+
export const Token = () => {
52+
const rippledSocket = useContext(SocketContext)
4953
const { trackScreenLoaded } = useAnalytics()
5054
const { token = '' } = useRouteParams(TOKEN_ROUTE)
5155
const [currency, accountId] = token.split('.')
5256
const { t } = useTranslation()
57+
const {
58+
data: tokenData,
59+
error: tokenDataError,
60+
isLoading: isTokenDataLoading,
61+
} = useQuery({
62+
queryKey: ['token', currency, accountId],
63+
queryFn: () => getToken(currency, accountId, rippledSocket),
64+
})
5365

5466
useEffect(() => {
5567
trackScreenLoaded({
@@ -63,21 +75,31 @@ const Token: FC<{ error: string }> = ({ error }) => {
6375
}, [accountId, currency, trackScreenLoaded])
6476

6577
const renderError = () => {
66-
const message = getErrorMessage(error)
78+
const message = getErrorMessage(tokenDataError)
6779
return <NoMatch title={message.title} hints={message.hints} />
6880
}
6981

70-
if (error) {
82+
if (tokenDataError) {
7183
return <Page accountId={accountId}>{renderError()}</Page>
7284
}
7385

7486
return (
7587
<Page accountId={accountId}>
76-
{accountId && <TokenHeader accountId={accountId} currency={currency} />}
77-
{accountId && IS_MAINNET && (
88+
{isTokenDataLoading ? (
89+
<Loader />
90+
) : (
91+
tokenData && (
92+
<TokenHeader
93+
accountId={accountId}
94+
currency={currency}
95+
data={tokenData}
96+
/>
97+
)
98+
)}
99+
{accountId && tokenData && IS_MAINNET && (
78100
<DEXPairs accountId={accountId} currency={currency} />
79101
)}
80-
{accountId && (
102+
{accountId && tokenData && (
81103
<div className="section">
82104
<h2>{t('token_transactions')}</h2>
83105
<TokenTransactionTable accountId={accountId} currency={currency} />
@@ -91,7 +113,3 @@ const Token: FC<{ error: string }> = ({ error }) => {
91113
</Page>
92114
)
93115
}
94-
95-
export default connect((state: any) => ({
96-
error: state.accountHeader.status,
97-
}))(Token)

‎src/containers/Token/test/index.test.tsx

+23-31
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,32 @@
11
import { mount } from 'enzyme'
2-
import configureMockStore from 'redux-mock-store'
3-
import thunk from 'redux-thunk'
4-
import { Provider } from 'react-redux'
52
import { Route } from 'react-router-dom'
6-
import { initialState } from '../../../rootReducer'
73
import i18n from '../../../i18n/testConfig'
8-
import Token from '../index'
9-
import TokenHeader from '../TokenHeader'
4+
import { Token } from '../index'
5+
import { TokenHeader } from '../TokenHeader'
106
import { TokenTransactionTable } from '../TokenTransactionTable'
11-
import mockAccountState from '../../Accounts/test/mockAccountState.json'
12-
import { QuickHarness } from '../../test/utils'
7+
import { flushPromises, QuickHarness } from '../../test/utils'
138
import { TOKEN_ROUTE } from '../../App/routes'
9+
import mockAccount from '../../Accounts/test/mockAccountState.json'
10+
import Mock = jest.Mock
11+
import { getToken } from '../../../rippled'
12+
13+
jest.mock('../../../rippled', () => ({
14+
__esModule: true,
15+
getToken: jest.fn(),
16+
}))
1417

1518
describe('Token container', () => {
1619
const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT'
1720

18-
const middlewares = [thunk]
19-
const mockStore = configureMockStore(middlewares)
20-
const createWrapper = (state = {}) => {
21-
const store = mockStore({ ...initialState, ...state })
21+
const createWrapper = (getAccountImpl = () => new Promise(() => {})) => {
22+
;(getToken as Mock).mockImplementation(getAccountImpl)
2223
return mount(
23-
<Provider store={store}>
24-
<QuickHarness
25-
i18n={i18n}
26-
initialEntries={[`/token/USD.${TEST_ACCOUNT_ID}`]}
27-
>
28-
<Route path={TOKEN_ROUTE.path} element={<Token />} />
29-
</QuickHarness>
30-
</Provider>,
24+
<QuickHarness
25+
i18n={i18n}
26+
initialEntries={[`/token/USD.${TEST_ACCOUNT_ID}`]}
27+
>
28+
<Route path={TOKEN_ROUTE.path} element={<Token />} />
29+
</QuickHarness>,
3130
)
3231
}
3332

@@ -36,17 +35,10 @@ describe('Token container', () => {
3635
wrapper.unmount()
3736
})
3837

39-
it('renders static parts', () => {
40-
const state = {
41-
...initialState,
42-
accountHeader: {
43-
loading: false,
44-
error: null,
45-
data: mockAccountState,
46-
},
47-
}
48-
49-
const wrapper = createWrapper(state)
38+
it('renders static parts', async () => {
39+
const wrapper = createWrapper(() => Promise.resolve(mockAccount))
40+
await flushPromises()
41+
wrapper.update()
5042
expect(wrapper.find(TokenHeader).length).toBe(1)
5143
expect(wrapper.find(TokenTransactionTable).length).toBe(1)
5244
wrapper.unmount()

‎src/rippled/token.js ‎src/rippled/token.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,26 @@ import { getBalances, getAccountInfo, getServerInfo } from './lib/rippled'
44

55
const log = logger({ name: 'iou' })
66

7-
const getToken = async (currencyCode, issuer, rippledSocket) => {
7+
export interface TokenData {
8+
name: string
9+
balance: string
10+
reserve: number
11+
sequence: number
12+
gravatar: string
13+
rate?: number
14+
obligations?: string
15+
domain?: string
16+
emailHash?: string
17+
previousLedger: number
18+
previousTxn: string
19+
flags: string[]
20+
}
21+
22+
const getToken = async (
23+
currencyCode,
24+
issuer,
25+
rippledSocket,
26+
): Promise<TokenData> => {
827
try {
928
log.info('fetching account info from rippled')
1029
const accountInfo = await getAccountInfo(rippledSocket, issuer)
@@ -47,7 +66,9 @@ const getToken = async (currencyCode, issuer, rippledSocket) => {
4766
previousLedger,
4867
}
4968
} catch (error) {
50-
log.error(error.toString())
69+
if (error) {
70+
log.error(error.toString())
71+
}
5172
throw error
5273
}
5374
}

‎src/rootReducer.js

-5
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@ import { combineReducers } from 'redux'
22
import accountHeaderReducer, {
33
initialState as accountHeaderState,
44
} from './containers/Accounts/AccountHeader/reducer'
5-
import tokenHeaderReducer, {
6-
initialState as tokenHeaderState,
7-
} from './containers/Token/TokenHeader/reducer'
85

96
export const initialState = {
107
accountHeader: accountHeaderState,
11-
tokenHeader: tokenHeaderState,
128
}
139

1410
const rootReducer = combineReducers({
1511
accountHeader: accountHeaderReducer,
16-
tokenHeader: tokenHeaderReducer,
1712
})
1813

1914
export default rootReducer

0 commit comments

Comments
 (0)
Please sign in to comment.