feat(tokenselector): 2 - add useDeferredVisibility hook#6519
feat(tokenselector): 2 - add useDeferredVisibility hook#6519fairlighteth merged 27 commits intodevelopfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughA new Changes
Sequence DiagramsequenceDiagram
participant Component as TokenListItem
participant Hook as useDeferredVisibility
participant Observer as IntersectionObserver
Component->>Hook: Mount with ref & options
Hook->>Hook: Initialize state (isVisible=false)
alt IntersectionObserver Available
Hook->>Observer: Create observer with rootMargin
Observer->>Observer: Monitor element visibility
Observer-->>Hook: Element intersects viewport
Hook->>Hook: Set isVisible=true
else SSR/Unsupported
Hook->>Hook: Immediately set isVisible=true
end
Hook-->>Component: Return ref + isVisible state
Component->>Component: Conditionally render:<br/>- ClickableAddress<br/>- TokenTags<br/>- TokenBalanceColumn
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx (1)
62-67: Excellent integration of deferred visibility.The
tokenKeyconstruction at line 62 provides a stable identity for each unique token by combiningchainIdand the lowercasedaddress. This ensures theresetKeyproperly triggers re-hydration when the same DOM element is reused to render different tokens in virtualized scenarios.The hook usage with
rootMargin: '200px'is appropriate for pre-loading content just before it enters the viewport, balancing performance with user experience.Optional suggestion: Consider extracting the hardcoded
'200px'to a named constant (e.g.,TOKEN_ROW_INTERSECTION_MARGIN) for easier maintenance and consistency if this value is used elsewhere or might need tuning.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/cowswap-frontend/src/modules/tokensList/hooks/useDeferredVisibility.ts(1 hunks)apps/cowswap-frontend/src/modules/tokensList/pure/TokenInfo/index.tsx(1 hunks)apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx(4 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-08-08T13:55:17.528Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/state/tokens/allTokensAtom.ts:78-78
Timestamp: 2025-08-08T13:55:17.528Z
Learning: In libs/tokens/src/state/tokens/allTokensAtom.ts (TypeScript/Jotai), the team prefers to wait for token lists to initialize (listsStatesListAtom non-empty) before returning tokens. No fallback to favorites/user-added/native tokens should be used when listsStatesList is empty.
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx
📚 Learning: 2025-08-08T13:56:18.009Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/updaters/TokensListsUpdater/index.tsx:29-31
Timestamp: 2025-08-08T13:56:18.009Z
Learning: In libs/tokens/src/updaters/TokensListsUpdater/index.tsx, the project’s current Jotai version requires using `unstable_getOnInit` (not `getOnInit`) in atomWithStorage options; keep `{ unstable_getOnInit: true }` until Jotai is upgraded.
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsxapps/cowswap-frontend/src/modules/tokensList/pure/TokenInfo/index.tsx
📚 Learning: 2025-07-24T16:42:53.154Z
Learnt from: cowdan
Repo: cowprotocol/cowswap PR: 6009
File: apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/HighFeeWarningTooltipContent.tsx:23-33
Timestamp: 2025-07-24T16:42:53.154Z
Learning: In apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/HighFeeWarningTooltipContent.tsx, the use of toFixed(2) for percentage formatting in tooltip content is intentional and differs from the banner message formatting that uses toSignificant(2, undefined, Rounding.ROUND_DOWN). This formatting difference serves different UX purposes and should not be flagged as inconsistent.
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx
📚 Learning: 2025-08-12T06:33:19.348Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6137
File: libs/tokens/src/state/tokens/allTokensAtom.ts:34-65
Timestamp: 2025-08-12T06:33:19.348Z
Learning: In libs/tokens/src/utils/parseTokenInfo.ts, the parseTokenInfo() function returns a new instance of TokenInfo using object spread syntax ({ ...token, ... }), making it safe to mutate properties like lpTokenProvider on the returned object without side effects.
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsxapps/cowswap-frontend/src/modules/tokensList/pure/TokenInfo/index.tsx
📚 Learning: 2025-10-10T20:28:16.565Z
Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 6347
File: apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx:49-49
Timestamp: 2025-10-10T20:28:16.565Z
Learning: In apps/cowswap-frontend/src/modules/trade, TradeConfirmation follows a two-layer architecture: TradeConfirmationView (pure/stateless) in pure/TradeConfirmation/index.tsx renders the UI, while TradeConfirmation (container) in containers/TradeConfirmation/index.tsx wraps it to freeze props during pending trades (via useStableTradeConfirmationProps), wire in signing state (useSigningStep), and inject trade confirmation state (useTradeConfirmState). Consuming modules should import the container TradeConfirmation from 'modules/trade' to preserve this stateful behavior.
<!-- [add_learning]
When reviewing component refactoring in apps/cowswap-frontend/src/modules/trade, recognize the pattern where a pure view component (e.g., TradeConfirmationView) is separated from a stateful container (e.g., TradeConfirmation) that wraps it. The container adds runtime state management (prop stabilization, signing state, etc.) while the view remains testable and composable. Do not flag usages that import th...
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx
📚 Learning: 2025-08-12T06:33:19.348Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6137
File: libs/tokens/src/state/tokens/allTokensAtom.ts:34-65
Timestamp: 2025-08-12T06:33:19.348Z
Learning: In libs/tokens/src/state/tokens/allTokensAtom.ts, the parseTokenInfo() function returns a new instance of TokenInfo each time, making it safe to mutate properties like lpTokenProvider on the returned object without side effects.
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenInfo/index.tsx
📚 Learning: 2025-09-19T11:38:59.206Z
Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 6232
File: apps/cowswap-frontend/src/modules/tokensList/pure/ChainsSelector/index.tsx:199-200
Timestamp: 2025-09-19T11:38:59.206Z
Learning: The makeBuildClickEvent function in apps/cowswap-frontend/src/modules/tokensList/pure/ChainsSelector/index.tsx takes five parameters: defaultChainId, contextLabel, mode, isSwapMode, and chainsCount. The chainsCount parameter is used to determine the CrossChain flag in analytics events.
Applied to files:
apps/cowswap-frontend/src/modules/tokensList/pure/TokenInfo/index.tsx
🔇 Additional comments (5)
apps/cowswap-frontend/src/modules/tokensList/pure/TokenInfo/index.tsx (1)
15-27: LGTM! Clean conditional rendering with backward compatibility.The addition of the optional
showAddressprop with a default value oftrueensures existing usage remains unaffected while enabling the deferred rendering strategy. The conditional rendering at line 27 is straightforward and correct.apps/cowswap-frontend/src/modules/tokensList/hooks/useDeferredVisibility.ts (1)
24-68: Solid implementation with correct one-time hydration pattern.The hook correctly implements deferred visibility using IntersectionObserver:
- The effect dependencies
[element, isVisible, rootMargin]are accurate.- The early return at line 40 when
isVisibleis true prevents re-observation, implementing the intended one-time hydration behavior.- The
resetKeyeffect (lines 31-37) properly resets visibility when token data changes, enabling correct re-hydration in virtualized list scenarios.- The ref callback (lines 63-65) is stable with empty dependencies, avoiding unnecessary re-renders.
- Fallback to immediate visibility when
IntersectionObserveris unavailable handles SSR and older browsers gracefully.- The cleanup function at line 60 ensures the observer is properly disconnected, preventing memory leaks.
Note: If
rootMarginwere passed dynamically (e.g., inline string on each render), it would cause the observer to be recreated unnecessarily. However, the current usage inTokenListItem(line 66) uses a static'200px'string, so this is not a concern in practice.apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx (3)
85-89: Well-structured deferred balance formatting.The two-level gating is correct:
shouldShowBalancesensures balances are only shown when the wallet is connected and the chain is supported.shouldFormatBalancesdefers the expensiveCurrencyAmount.fromRawAmountconversion until after the row has intersected the viewport.This approach successfully delays the computational cost of balance formatting until it's actually needed.
101-113: Correct conditional rendering based on intersection.Both the
showAddressprop (line 103) and theTokenTagsrendering (lines 105-112) are correctly gated byhasIntersected, ensuring these UI elements are only hydrated once the row is near the viewport. This aligns with the performance goals stated in the PR objectives.
126-155: TokenBalanceColumn correctly implements the three-state balance UI.The component properly handles:
- Hidden state (
!shouldShow): Returnsnullwhen wallet is disconnected or chain is unsupported.- Loading state (
shouldShow && !shouldFormat): ShowsLoadingElementbefore the row intersects the viewport.- Formatted state (
shouldFormat): Displays formatted balance and fiat amount after intersection.The fallback to
LoadingElementat line 147 whenbalanceAmountis undefined (balance not yet fetched) is reasonable and preserves the original behavior while adding deferred rendering.
| className, | ||
| } = props | ||
|
|
||
| const tokenKey = `${token.chainId}:${token.address.toLowerCase()}` |
There was a problem hiding this comment.
nitpick: not a very big deal for this pr, but we need to unify methods like comparing addresses and getting id in helpers due to solana/other networks integrations (f.e. in solana the upper and lower case matters). It will be better to define method like getTokenId(token): string
jfyi
There was a problem hiding this comment.
Addressed in a follow up PR (11) -> #6542
Summary
This PR delivers the token-row performance foundations from the split plan: a shared
useDeferredVisibilityhook plus deferred rendering for token tags/address and balance/fiat formatting so the selector only hydrates rows that are near the viewport.To Test
Token selector scrolling
Wallet disconnected
Re-filtering
Background
Part of the staged token-selector rollout, focusing on deferring expensive row work (logos, tags, fiat) until necessary.
Summary by CodeRabbit
Release Notes