Skip to content

Commit cfb7a98

Browse files
committed
refactor: extract realtime data logic into reusable hooks and components
- Create custom hooks for data fetching: * useTokenRateLimits - single lane rate limits * useMultiLaneRateLimits - multiple lanes in parallel * useTokenFinality - token finality data - Add RateLimitCell component to replace complex nested ternaries - Create singleton realtimeDataService instance to avoid multiple instantiations - Add rate-limit-formatter utilities for consistent formatting - Update LaneDrawer, TokenDrawer, and TokenChainsTable to use new hooks - Add proper cleanup in useEffect hooks to prevent memory leaks Benefits: - Reduced code duplication (~133 lines removed) - Improved readability with <RateLimitCell> vs 7-level nested ternaries - Better separation of concerns - More testable code - Proper cleanup handling
1 parent 69a5b9d commit cfb7a98

File tree

9 files changed

+385
-182
lines changed

9 files changed

+385
-182
lines changed

src/components/CCIP/Drawer/LaneDrawer.tsx

Lines changed: 24 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import "../Tables/Table.css"
33
import { Environment, LaneConfig, LaneFilter, Version } from "~/config/data/ccip/types.ts"
44
import { getNetwork, getTokenData } from "~/config/data/ccip/data.ts"
55
import { determineTokenMechanism } from "~/config/data/ccip/utils.ts"
6-
import { useState, useEffect } from "react"
6+
import { useState } from "react"
77
import LaneDetailsHero from "../ChainHero/LaneDetailsHero.tsx"
88
import { getExplorerAddressUrl, getTokenIconUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts"
99
import TableSearchInput from "../Tables/TableSearchInput.tsx"
1010
import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx"
1111
import { ChainType, ExplorerInfo } from "@config/types.ts"
12-
import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts"
13-
import type { TokenRateLimits } from "~/lib/ccip/types/index.ts"
12+
import { useTokenRateLimits } from "~/hooks/useTokenRateLimits.ts"
13+
import { RateLimitCell } from "~/components/CCIP/RateLimitCell.tsx"
14+
import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts"
1415

1516
function LaneDrawer({
1617
lane,
@@ -28,8 +29,6 @@ function LaneDrawer({
2829
inOutbound: LaneFilter
2930
}) {
3031
const [search, setSearch] = useState("")
31-
const [rateLimits, setRateLimits] = useState<Record<string, TokenRateLimits>>({})
32-
const [isLoadingRateLimits, setIsLoadingRateLimits] = useState(true)
3332

3433
const destinationNetworkDetails = getNetwork({
3534
filter: environment,
@@ -41,26 +40,12 @@ function LaneDrawer({
4140
chain: sourceNetwork.key,
4241
})
4342

44-
// Fetch rate limits data
45-
useEffect(() => {
46-
const fetchRateLimits = async () => {
47-
setIsLoadingRateLimits(true)
48-
const realtimeService = new RealtimeDataService()
43+
// Determine source and destination based on inOutbound filter
44+
const source = inOutbound === LaneFilter.Outbound ? sourceNetwork.key : destinationNetwork.key
45+
const destination = inOutbound === LaneFilter.Outbound ? destinationNetwork.key : sourceNetwork.key
4946

50-
// Determine source and destination based on inOutbound filter
51-
const source = inOutbound === LaneFilter.Outbound ? sourceNetwork.key : destinationNetwork.key
52-
const destination = inOutbound === LaneFilter.Outbound ? destinationNetwork.key : sourceNetwork.key
53-
54-
const response = await realtimeService.getLaneSupportedTokens(source, destination, environment)
55-
56-
if (response?.data) {
57-
setRateLimits(response.data)
58-
}
59-
setIsLoadingRateLimits(false)
60-
}
61-
62-
fetchRateLimits()
63-
}, [sourceNetwork.key, destinationNetwork.key, environment, inOutbound])
47+
// Fetch rate limits data using custom hook
48+
const { rateLimits, isLoading: isLoadingRateLimits } = useTokenRateLimits(source, destination, environment)
6449

6550
return (
6651
<>
@@ -212,28 +197,18 @@ function LaneDrawer({
212197

213198
// Get rate limit data for this token
214199
const tokenRateLimits = rateLimits[token]
215-
const realtimeService = new RealtimeDataService()
216200

217201
// Determine direction based on inOutbound filter
218202
const direction = inOutbound === LaneFilter.Outbound ? "out" : "in"
219203

220204
// Get standard and FTF rate limits
221205
const allLimits = tokenRateLimits
222-
? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction)
206+
? realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction)
223207
: { standard: null, ftf: null }
224208

225209
// Token is paused if standard rate limit capacity is 0
226210
const tokenPaused = allLimits.standard?.capacity === "0"
227211

228-
// Format rate limit values
229-
const formatRateLimit = (value: string | null) => {
230-
if (!value || value === "0") return "0"
231-
// Convert from wei to tokens (divide by 1e18)
232-
const numValue = BigInt(value)
233-
const formatted = Number(numValue) / 1e18
234-
return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 })
235-
}
236-
237212
return (
238213
<tr key={index} className={tokenPaused ? "ccip-table__row--paused" : ""}>
239214
<td>
@@ -280,68 +255,26 @@ function LaneDrawer({
280255
</td>
281256

282257
<td>
283-
{isLoadingRateLimits ? (
284-
"Loading..."
285-
) : allLimits.standard ? (
286-
allLimits.standard.isEnabled ? (
287-
formatRateLimit(allLimits.standard.capacity)
288-
) : (
289-
"Disabled"
290-
)
291-
) : (
292-
<span style={{ display: "inline-flex", alignItems: "center", gap: "4px" }}>
293-
Unavailable
294-
<Tooltip
295-
label=""
296-
tip="Rate limit data is currently unavailable. You can find the Token Pool rate limit by reading the Token Pool contract directly on the relevant blockchain."
297-
style={{
298-
display: "inline-block",
299-
verticalAlign: "middle",
300-
}}
301-
/>
302-
</span>
303-
)}
258+
<RateLimitCell
259+
isLoading={isLoadingRateLimits}
260+
rateLimit={allLimits.standard}
261+
type="capacity"
262+
showUnavailableTooltip
263+
/>
304264
</td>
305265
<td className="rate-tooltip-cell">
306-
{isLoadingRateLimits
307-
? "Loading..."
308-
: allLimits.standard
309-
? allLimits.standard.isEnabled
310-
? formatRateLimit(allLimits.standard.rate)
311-
: "Disabled"
312-
: "N/A"}
266+
<RateLimitCell isLoading={isLoadingRateLimits} rateLimit={allLimits.standard} type="rate" />
313267
</td>
314268
<td>
315-
{isLoadingRateLimits ? (
316-
"Loading..."
317-
) : allLimits.ftf ? (
318-
allLimits.ftf.isEnabled ? (
319-
formatRateLimit(allLimits.ftf.capacity)
320-
) : (
321-
"Disabled"
322-
)
323-
) : (
324-
<span style={{ display: "inline-flex", alignItems: "center", gap: "4px" }}>
325-
Unavailable
326-
<Tooltip
327-
label=""
328-
tip="Rate limit data is currently unavailable. You can find the Token Pool rate limit by reading the Token Pool contract directly on the relevant blockchain."
329-
style={{
330-
display: "inline-block",
331-
verticalAlign: "middle",
332-
}}
333-
/>
334-
</span>
335-
)}
269+
<RateLimitCell
270+
isLoading={isLoadingRateLimits}
271+
rateLimit={allLimits.ftf}
272+
type="capacity"
273+
showUnavailableTooltip
274+
/>
336275
</td>
337276
<td>
338-
{isLoadingRateLimits
339-
? "Loading..."
340-
: allLimits.ftf
341-
? allLimits.ftf.isEnabled
342-
? formatRateLimit(allLimits.ftf.rate)
343-
: "Disabled"
344-
: "N/A"}
277+
<RateLimitCell isLoading={isLoadingRateLimits} rateLimit={allLimits.ftf} type="rate" />
345278
</td>
346279
</tr>
347280
)

src/components/CCIP/Drawer/TokenDrawer.tsx

Lines changed: 23 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ import {
1515
getVerifiersByNetwork,
1616
getVerifierTypeDisplay,
1717
} from "~/config/data/ccip/index.ts"
18-
import { useState, useEffect } from "react"
18+
import { useState, useMemo } from "react"
1919
import { ChainType, ExplorerInfo, SupportedChain } from "~/config/index.ts"
2020
import { getExplorerAddressUrl } from "~/features/utils/index.ts"
2121
import Address from "~/components/AddressReact.tsx"
2222
import LaneDrawer from "../Drawer/LaneDrawer.tsx"
2323
import TableSearchInput from "../Tables/TableSearchInput.tsx"
2424
import Tabs from "../Tables/Tabs.tsx"
2525
import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx"
26-
import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts"
27-
import type { TokenRateLimits } from "~/lib/ccip/types/index.ts"
26+
import { useMultiLaneRateLimits } from "~/hooks/useMultiLaneRateLimits.ts"
27+
import { RateLimitCell } from "~/components/CCIP/RateLimitCell.tsx"
28+
import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts"
2829

2930
function TokenDrawer({
3031
token,
@@ -60,7 +61,6 @@ function TokenDrawer({
6061
}) {
6162
const [search, setSearch] = useState("")
6263
const [activeTab, setActiveTab] = useState<"outbound" | "inbound" | "verifiers">("outbound")
63-
const [rateLimits, setRateLimits] = useState<Record<string, Record<string, TokenRateLimits>>>({})
6464

6565
// Get verifiers for the current network
6666
const verifiers = getVerifiersByNetwork({
@@ -79,28 +79,16 @@ function TokenDrawer({
7979
destinationPoolType: PoolType
8080
}
8181

82-
// Fetch rate limits for all lanes
83-
useEffect(() => {
84-
const fetchAllRateLimits = async () => {
85-
const realtimeService = new RealtimeDataService()
86-
const newRateLimits: Record<string, Record<string, TokenRateLimits>> = {}
82+
// Build lane configurations for fetching rate limits
83+
const laneConfigs = useMemo(() => {
84+
return Object.keys(destinationLanes).map((destinationChain) => ({
85+
source: activeTab === "outbound" ? network.key : destinationChain,
86+
destination: activeTab === "outbound" ? destinationChain : network.key,
87+
}))
88+
}, [destinationLanes, network.key, activeTab])
8789

88-
for (const destinationChain of Object.keys(destinationLanes)) {
89-
const source = activeTab === "outbound" ? network.key : destinationChain
90-
const destination = activeTab === "outbound" ? destinationChain : network.key
91-
const laneKey = `${source}-${destination}`
92-
93-
const response = await realtimeService.getLaneSupportedTokens(source, destination, environment)
94-
if (response?.data) {
95-
newRateLimits[laneKey] = response.data
96-
}
97-
}
98-
99-
setRateLimits(newRateLimits)
100-
}
101-
102-
fetchAllRateLimits()
103-
}, [network.key, destinationLanes, environment, activeTab])
90+
// Fetch rate limits for all lanes using custom hook
91+
const { rateLimitsMap, isLoading: isLoadingRateLimits } = useMultiLaneRateLimits(laneConfigs, environment)
10492

10593
const laneRows: LaneRow[] = Object.keys(destinationLanes)
10694
.map((destinationChain) => {
@@ -318,30 +306,19 @@ function TokenDrawer({
318306
const source = activeTab === "outbound" ? network.key : destinationChain
319307
const destination = activeTab === "outbound" ? destinationChain : network.key
320308
const laneKey = `${source}-${destination}`
321-
const laneRateLimits = rateLimits[laneKey]
309+
const laneRateLimits = rateLimitsMap[laneKey]
322310
const tokenRateLimits = laneRateLimits?.[token.id]
323311

324-
const realtimeService = new RealtimeDataService()
325312
const direction = activeTab === "outbound" ? "out" : "in"
326313

327314
// Get standard and FTF rate limits
328315
const allLimits = tokenRateLimits
329-
? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction)
316+
? realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction)
330317
: { standard: null, ftf: null }
331318

332319
// Token is paused if standard rate limit capacity is 0
333320
const tokenPaused = allLimits.standard?.capacity === "0"
334321

335-
// Format rate limit values
336-
const formatRateLimit = (value: string | null) => {
337-
if (!value || value === "0") return "0"
338-
const numValue = BigInt(value)
339-
const formatted = Number(numValue) / 1e18
340-
return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 })
341-
}
342-
343-
const isLoading = !laneRateLimits
344-
345322
return (
346323
<tr key={networkDetails.name} className={tokenPaused ? "ccip-table__row--paused" : ""}>
347324
<td>
@@ -381,40 +358,20 @@ function TokenDrawer({
381358
</button>
382359
</td>
383360
<td>
384-
{isLoading
385-
? "Loading..."
386-
: allLimits.standard
387-
? allLimits.standard.isEnabled
388-
? formatRateLimit(allLimits.standard.capacity)
389-
: "Disabled"
390-
: "N/A"}
361+
<RateLimitCell
362+
isLoading={isLoadingRateLimits}
363+
rateLimit={allLimits.standard}
364+
type="capacity"
365+
/>
391366
</td>
392367
<td>
393-
{isLoading
394-
? "Loading..."
395-
: allLimits.standard
396-
? allLimits.standard.isEnabled
397-
? formatRateLimit(allLimits.standard.rate)
398-
: "Disabled"
399-
: "N/A"}
368+
<RateLimitCell isLoading={isLoadingRateLimits} rateLimit={allLimits.standard} type="rate" />
400369
</td>
401370
<td>
402-
{isLoading
403-
? "Loading..."
404-
: allLimits.ftf
405-
? allLimits.ftf.isEnabled
406-
? formatRateLimit(allLimits.ftf.capacity)
407-
: "Disabled"
408-
: "N/A"}
371+
<RateLimitCell isLoading={isLoadingRateLimits} rateLimit={allLimits.ftf} type="capacity" />
409372
</td>
410373
<td>
411-
{isLoading
412-
? "Loading..."
413-
: allLimits.ftf
414-
? allLimits.ftf.isEnabled
415-
? formatRateLimit(allLimits.ftf.rate)
416-
: "Disabled"
417-
: "N/A"}
374+
<RateLimitCell isLoading={isLoadingRateLimits} rateLimit={allLimits.ftf} type="rate" />
418375
</td>
419376
<td>
420377
{activeTab === "outbound"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx"
2+
import type { RateLimiterConfig } from "~/lib/ccip/types/index.ts"
3+
import { formatRateLimit } from "~/lib/ccip/utils/rate-limit-formatter.ts"
4+
5+
interface RateLimitCellProps {
6+
isLoading: boolean
7+
rateLimit: RateLimiterConfig | null | undefined
8+
type: "capacity" | "rate"
9+
showUnavailableTooltip?: boolean
10+
}
11+
12+
/**
13+
* Component for displaying rate limit values in table cells
14+
* Handles loading, disabled, unavailable, and value states
15+
*/
16+
export function RateLimitCell({ isLoading, rateLimit, type, showUnavailableTooltip = false }: RateLimitCellProps) {
17+
if (isLoading) {
18+
return <>Loading...</>
19+
}
20+
21+
if (!rateLimit) {
22+
if (showUnavailableTooltip) {
23+
return (
24+
<span style={{ display: "inline-flex", alignItems: "center", gap: "4px" }}>
25+
Unavailable
26+
<Tooltip
27+
label=""
28+
tip="Rate limit data is currently unavailable. You can find the Token Pool rate limit by reading the Token Pool contract directly on the relevant blockchain."
29+
style={{
30+
display: "inline-block",
31+
verticalAlign: "middle",
32+
}}
33+
/>
34+
</span>
35+
)
36+
}
37+
return <>N/A</>
38+
}
39+
40+
if (!rateLimit.isEnabled) {
41+
return <>Disabled</>
42+
}
43+
44+
const value = type === "capacity" ? rateLimit.capacity : rateLimit.rate
45+
return <>{formatRateLimit(value)}</>
46+
}

0 commit comments

Comments
 (0)