@@ -4,63 +4,50 @@ import { IoSearch } from 'react-icons/io5'
4
4
import { IoMdClose } from 'react-icons/io'
5
5
import { IoChevronDown } from 'react-icons/io5'
6
6
import axios from 'axios'
7
- import {
8
- avatarServer ,
9
- nativeCurrency ,
10
- isNativeCurrency ,
11
- nativeCurrenciesImages ,
12
- useWidth ,
13
- setTabParams
14
- } from '../../utils'
15
- import { niceCurrency , shortAddress , shortNiceNumber , amountFormat } from '../../utils/format'
7
+ import { nativeCurrency , nativeCurrenciesImages , useWidth , setTabParams , tokenImageSrc } from '../../utils'
8
+ import { niceCurrency , shortAddress , shortNiceNumber } from '../../utils/format'
16
9
import RadioOptions from './RadioOptions'
17
10
import { useRouter } from 'next/router'
18
11
19
12
const limit = 20
20
13
21
14
// Helper function to fetch and process trustlines for a destination address
22
15
const fetchTrustlinesForDestination = async ( destinationAddress , searchQuery = '' ) => {
23
- const response = await axios ( `v2/objects /${ destinationAddress } ?limit=1000&type=state ` )
24
- const objects = response . data ?. objects || [ ]
16
+ const response = await axios ( `v2/address /${ destinationAddress } /acceptedTokens ?limit=${ limit } ` )
17
+ const tokens = response . data ?. tokens || [ ]
25
18
26
- // Filter RippleState objects to get trustlines where destination can hold tokens
27
- const trustlines = objects . filter ( ( obj ) => {
28
- if ( parseFloat ( obj . LowLimit . value ) <= 0 && parseFloat ( obj . HighLimit . value ) <= 0 ) return false
19
+ // Trim the search query to handle whitespace
20
+ const trimmedQuery = searchQuery . trim ( )
29
21
22
+ const trustlines = tokens . filter ( ( token ) => {
30
23
// If search query is provided, filter by it
31
- if ( searchQuery ) {
32
- const currency = obj . Balance . currency
33
- const issuerDetails =
34
- obj . HighLimit . issuer === destinationAddress ? obj . LowLimit . issuerDetails : obj . HighLimit . issuerDetails || { }
35
- const serviceOrUsername = issuerDetails . service || issuerDetails . username || ''
36
- const issuer = obj . HighLimit . issuer === destinationAddress ? obj . LowLimit . issuer : obj . HighLimit . issuer || ''
37
-
38
- const searchLower = searchQuery . toLowerCase ( )
24
+ if ( trimmedQuery ) {
25
+ const currency = token . currency
26
+ const issuerDetails = token . issuerDetails || { }
27
+ const service = issuerDetails . service || ''
28
+ const username = issuerDetails . username || ''
29
+ const issuer = token . issuer || ''
30
+
31
+ const searchLower = trimmedQuery . toLowerCase ( )
39
32
return (
40
33
currency . toLowerCase ( ) . includes ( searchLower ) ||
41
- serviceOrUsername . toLowerCase ( ) . includes ( searchLower ) ||
34
+ service . toLowerCase ( ) . includes ( searchLower ) ||
35
+ username . toLowerCase ( ) . includes ( searchLower ) ||
42
36
issuer . toLowerCase ( ) . includes ( searchLower )
43
37
)
44
38
}
45
-
46
39
return true
47
40
} )
48
41
49
- // Convert trustlines to token format
50
- return trustlines . map ( ( tl ) => ( {
51
- currency : tl . Balance . currency ,
52
- issuer : tl . HighLimit . issuer === destinationAddress ? tl . LowLimit . issuer : tl . HighLimit . issuer ,
53
- issuerDetails : tl . HighLimit . issuer === destinationAddress ? tl . LowLimit . issuerDetails : tl . HighLimit . issuerDetails ,
54
- limit : Math . max ( parseFloat ( tl . LowLimit . value ) , parseFloat ( tl . HighLimit . value ) ) ,
55
- balance : tl . Balance . value
56
- } ) )
42
+ return trustlines
57
43
}
58
44
59
45
// Helper function to add native currency to tokens array if needed
60
46
const addNativeCurrencyIfNeeded = ( tokens , excludeNative , searchQuery = '' ) => {
61
47
if ( excludeNative ) return tokens
62
48
63
- const shouldAddNative = ! searchQuery || searchQuery . toUpperCase ( ) === nativeCurrency . toUpperCase ( )
49
+ const trimmedQuery = searchQuery . trim ( )
50
+ const shouldAddNative = ! trimmedQuery || trimmedQuery . toUpperCase ( ) === nativeCurrency . toUpperCase ( )
64
51
if ( shouldAddNative ) {
65
52
tokens . unshift ( { currency : nativeCurrency , limit : null } )
66
53
}
@@ -85,6 +72,10 @@ export default function TokenSelector({
85
72
const [ isLoading , setIsLoading ] = useState ( false )
86
73
const [ searchTimeout , setSearchTimeout ] = useState ( null )
87
74
75
+ // Cache for search results to prevent unnecessary reloads
76
+ const [ lastSearchQuery , setLastSearchQuery ] = useState ( '' )
77
+ const [ cachedSearchResults , setCachedSearchResults ] = useState ( [ ] )
78
+
88
79
// control radio selection: 'all' | 'single'
89
80
const [ filterMode , setFilterMode ] = useState ( ( ) => ( value ?. currency ? 'single' : 'all' ) )
90
81
@@ -120,6 +111,8 @@ export default function TokenSelector({
120
111
useEffect ( ( ) => {
121
112
setSearchResults ( [ ] )
122
113
setSearchQuery ( '' )
114
+ setLastSearchQuery ( '' )
115
+ setCachedSearchResults ( [ ] )
123
116
} , [ destinationAddress ] )
124
117
125
118
// Handle search with debounce
@@ -133,23 +126,11 @@ export default function TokenSelector({
133
126
}
134
127
135
128
const timeout = setTimeout ( async ( ) => {
136
- if ( ! searchQuery ) {
137
- // Only apply the early return logic when there's no destination address
138
- // When destination address is provided, we always want to fetch fresh data
139
- if ( ! destinationAddress ) {
140
- // do not reload default token list if it's already loaded
141
- // when searched for native currency, we also add the native currency on top,
142
- // so check that it's not that case before canceling the search
143
- if (
144
- isNativeCurrency ( searchResults [ 0 ] ) &&
145
- ! niceCurrency ( searchResults [ 1 ] ?. currency ) ?. toLowerCase ( ) . startsWith ( nativeCurrency . toLowerCase ( ) )
146
- )
147
- return
148
- } else {
149
- // For destination address case, check if we already have results loaded
150
- if ( searchResults . length > 0 ) {
151
- return
152
- }
129
+ if ( ! searchQuery . trim ( ) ) {
130
+ // Check if we have cached results for empty search query
131
+ if ( lastSearchQuery === '' && cachedSearchResults . length > 0 ) {
132
+ setSearchResults ( cachedSearchResults )
133
+ return
153
134
}
154
135
155
136
setIsLoading ( true )
@@ -159,51 +140,77 @@ export default function TokenSelector({
159
140
if ( destinationAddress ) {
160
141
// Fetch tokens that destination can hold based on trustlines
161
142
tokens = await fetchTrustlinesForDestination ( destinationAddress )
162
- tokens = addNativeCurrencyIfNeeded ( tokens , excludeNative )
163
143
} else {
164
144
// Fallback to original behavior if no destination address
165
145
const response = await axios ( 'v2/trustlines/tokens?limit=' + limit + '¤cyDetails=true' )
166
146
tokens = response . data ?. tokens || [ ]
167
147
if ( ! excludeNative ) {
168
- setSearchResults ( [ { currency : nativeCurrency } , ...tokens ] )
148
+ const defaultTokens = [ { currency : nativeCurrency } , ...tokens ]
149
+ setSearchResults ( defaultTokens )
150
+ // Cache the default token list
151
+ setLastSearchQuery ( '' )
152
+ setCachedSearchResults ( defaultTokens )
169
153
} else {
170
154
setSearchResults ( tokens )
155
+ // Cache the default token list
156
+ setLastSearchQuery ( '' )
157
+ setCachedSearchResults ( tokens )
171
158
}
172
159
setIsLoading ( false )
173
160
return
174
161
}
175
162
176
163
setSearchResults ( tokens )
164
+ // Cache the default token list for destination address case
165
+ setLastSearchQuery ( '' )
166
+ setCachedSearchResults ( tokens )
177
167
} catch ( error ) {
178
168
console . error ( 'Error loading tokens:' , error )
179
169
if ( excludeNative ) {
180
170
setSearchResults ( [ ] )
171
+ setLastSearchQuery ( '' )
172
+ setCachedSearchResults ( [ ] )
181
173
} else {
182
174
setSearchResults ( [ { currency : nativeCurrency } ] )
175
+ setLastSearchQuery ( '' )
176
+ setCachedSearchResults ( [ { currency : nativeCurrency } ] )
183
177
}
184
178
} finally {
185
179
setIsLoading ( false )
186
180
}
187
181
return
188
182
}
189
183
184
+ // Check if we have cached results for this search query
185
+ if ( lastSearchQuery === searchQuery ) {
186
+ setSearchResults ( cachedSearchResults )
187
+ return
188
+ }
189
+
190
190
setIsLoading ( true )
191
191
try {
192
192
if ( destinationAddress ) {
193
193
// For destination-specific search, filter the existing trustlines
194
194
const tokens = await fetchTrustlinesForDestination ( destinationAddress , searchQuery )
195
195
const tokensWithNative = addNativeCurrencyIfNeeded ( tokens , excludeNative , searchQuery )
196
196
setSearchResults ( tokensWithNative )
197
+ // Cache the results
198
+ setLastSearchQuery ( searchQuery )
199
+ setCachedSearchResults ( tokensWithNative )
197
200
} else {
198
201
// Fallback to original search behavior
199
202
const response = await axios ( `v2/trustlines/tokens/search/${ searchQuery } ?limit=${ limit } ¤cyDetails=true` )
200
203
const tokens = response . data ?. tokens || [ ]
201
204
const tokensWithNative = addNativeCurrencyIfNeeded ( tokens , excludeNative , searchQuery )
202
205
setSearchResults ( tokensWithNative )
206
+ // Cache the results
207
+ setLastSearchQuery ( searchQuery )
208
+ setCachedSearchResults ( tokensWithNative )
203
209
}
204
210
} catch ( error ) {
205
211
console . error ( 'Error searching tokens:' , error )
206
212
setSearchResults ( [ ] )
213
+ setCachedSearchResults ( [ ] )
207
214
} finally {
208
215
setIsLoading ( false )
209
216
}
@@ -222,12 +229,11 @@ export default function TokenSelector({
222
229
const handleSelect = ( token ) => {
223
230
onChange ( token )
224
231
setIsOpen ( false )
225
- setSearchQuery ( '' )
226
232
}
227
233
228
234
// Helper to get icon url if available
229
235
const getTokenIcon = ( token ) => {
230
- let imageUrl = avatarServer + token . issuer
236
+ let imageUrl = tokenImageSrc ( token )
231
237
if ( ! token . issuer ) {
232
238
imageUrl = nativeCurrenciesImages [ nativeCurrency ]
233
239
}
@@ -250,20 +256,6 @@ export default function TokenSelector({
250
256
return niceCurrency ( token . currency )
251
257
}
252
258
253
- // Helper to get token limit display
254
- const getTokenLimitDisplay = ( token ) => {
255
- if ( ! token . limit || token . currency === nativeCurrency ) return null
256
-
257
- return (
258
- < div className = "token-selector-modal-item-limit" >
259
- < span className = "token-selector-modal-item-limit-label" > Max:</ span >
260
- < span className = "token-selector-modal-item-limit-value" >
261
- { amountFormat ( { value : token . limit , currency : token . currency , issuer : token . issuer } , { short : true } ) }
262
- </ span >
263
- </ div >
264
- )
265
- }
266
-
267
259
return (
268
260
< >
269
261
{ allOrOne && (
@@ -373,7 +365,6 @@ export default function TokenSelector({
373
365
) }
374
366
</ span >
375
367
{ width > 1100 ? < span > { token . issuer } </ span > : < span > { shortAddress ( token . issuer ) } </ span > }
376
- { getTokenLimitDisplay ( token ) }
377
368
</ div >
378
369
</ div >
379
370
</ div >
0 commit comments