From 7ae7b31ff9dbc5e8c0c8a2ec657bdd6eac3e2a11 Mon Sep 17 00:00:00 2001 From: umeeSthein Date: Thu, 2 Apr 2026 11:50:42 +0200 Subject: [PATCH 1/2] fix(explorer): avoid holdings state updates during render --- .../src/routes/_layout/address/$address.tsx | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/apps/explorer/src/routes/_layout/address/$address.tsx b/apps/explorer/src/routes/_layout/address/$address.tsx index a9a1251a2..911bcda65 100644 --- a/apps/explorer/src/routes/_layout/address/$address.tsx +++ b/apps/explorer/src/routes/_layout/address/$address.tsx @@ -1284,12 +1284,11 @@ function SectionsWrapper(props: { // Holdings uses local pagination state (decoupled from URL `page` param) const [holdingsPage, setHoldingsPage] = React.useState(1) const [showAllHoldings, setShowAllHoldings] = React.useState(false) - const prevAddressRef = React.useRef(address) - if (prevAddressRef.current !== address) { - prevAddressRef.current = address - setHoldingsPage(1) - setShowAllHoldings(false) - } + // Render a new address with default holdings controls immediately, then + // sync the backing state after commit. + const settledHoldingsAddressRef = React.useRef(address) + const isHoldingsAddressTransition = + settledHoldingsAddressRef.current !== address const { isTokenListed: isHoldingTokenListed } = useTokenListMembership() const { listedAssets, unlistedAssets } = React.useMemo(() => { @@ -1305,7 +1304,10 @@ function SectionsWrapper(props: { return { listedAssets: listed, unlistedAssets: unlisted } }, [assetsData, isHoldingTokenListed]) - const visibleAssets = showAllHoldings ? assetsData : listedAssets + const currentShowAllHoldings = isHoldingsAddressTransition + ? false + : showAllHoldings + const visibleAssets = currentShowAllHoldings ? assetsData : listedAssets const hasUnlisted = unlistedAssets.length > 0 // Clamp page when asset count shrinks (e.g. after a refetch or filter toggle) @@ -1313,9 +1315,23 @@ function SectionsWrapper(props: { 1, Math.ceil(visibleAssets.length / ASSETS_PER_PAGE), ) - if (holdingsPage > maxHoldingsPage) { + const currentHoldingsPage = isHoldingsAddressTransition + ? 1 + : Math.min(holdingsPage, maxHoldingsPage) + + React.useEffect(() => { + if (!isHoldingsAddressTransition) return + + settledHoldingsAddressRef.current = address + setHoldingsPage(1) + setShowAllHoldings(false) + }, [address, isHoldingsAddressTransition]) + + React.useEffect(() => { + if (isHoldingsAddressTransition || holdingsPage <= maxHoldingsPage) return + setHoldingsPage(maxHoldingsPage) - } + }, [holdingsPage, isHoldingsAddressTransition, maxHoldingsPage]) // Build sections based on visible tabs const sections = visibleTabs.map((tabName) => { @@ -1401,7 +1417,7 @@ function SectionsWrapper(props: { ), } case 'holdings': { - const holdingsPages = Math.ceil(visibleAssets.length / ASSETS_PER_PAGE) + const holdingsPages = maxHoldingsPage return { title: 'Holdings', totalItems: visibleAssets.length, @@ -1425,8 +1441,8 @@ function SectionsWrapper(props: { items={(mode) => visibleAssets .slice( - (holdingsPage - 1) * ASSETS_PER_PAGE, - holdingsPage * ASSETS_PER_PAGE, + (currentHoldingsPage - 1) * ASSETS_PER_PAGE, + currentHoldingsPage * ASSETS_PER_PAGE, ) .map((asset) => ({ className: 'text-[13px]', @@ -1453,17 +1469,17 @@ function SectionsWrapper(props: { } totalItems={visibleAssets.length} displayCount={visibleAssets.length} - page={holdingsPage} + page={currentHoldingsPage} itemsLabel="assets" itemsPerPage={ASSETS_PER_PAGE} pagination={ { setShowAllHoldings((prev) => !prev) From b89e400c34148b9e57f27b6f65acb13ac2f621e2 Mon Sep 17 00:00:00 2001 From: umeeSthein Date: Mon, 4 May 2026 18:07:51 +0700 Subject: [PATCH 2/2] fix(explorer): avoid stale holdings data during address transitions --- apps/explorer/src/routes/_layout/address/$address.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/explorer/src/routes/_layout/address/$address.tsx b/apps/explorer/src/routes/_layout/address/$address.tsx index 911bcda65..8412e95f2 100644 --- a/apps/explorer/src/routes/_layout/address/$address.tsx +++ b/apps/explorer/src/routes/_layout/address/$address.tsx @@ -520,6 +520,7 @@ function RouteComponent() { const { address } = Route.useParams() const { page, tab, live, limit, status, dir, period } = Route.useSearch() const { + address: loaderAddress, accountType, isToken, tokenMetadata, @@ -626,11 +627,13 @@ function RouteComponent() { visibleTabs.indexOf(tab) !== -1 ? visibleTabs.indexOf(tab) : 0 const isHoldingsTabActive = tab === 'holdings' + const currentBalancesData = + loaderAddress === address ? balancesData : undefined const { data: assetsData, isLoading: assetsLoading } = useBalancesData( address, - balancesData, - !isToken && (isHoldingsTabActive || balancesData !== undefined), + currentBalancesData, + !isToken && (isHoldingsTabActive || currentBalancesData !== undefined), ) const historySources = React.useMemo( () => historySourcesForAddress(address),