Skip to content

Commit a9026e3

Browse files
refactor: compute cache bust flags at transaction submission time
Move cache bust logic from TransactionNotifications callback to transaction submission time when the full transaction data is available. This ensures accurate detection of avatar/header changes for all transaction types. - Add computeCacheBustFlags utility to centralize cache bust logic - Store cacheBust flags in transaction store for use after confirmation - Simplify TransactionNotifications to use pre-computed flags - Add hasMediaRecordChange helpers for detecting record changes - Fix migrateProfile/migrateProfileWithReset to return undefined (no bust needed) - Fix resetProfileWithRecords to always bust cache (uses clearRecords: true) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent eb5c414 commit a9026e3

File tree

7 files changed

+341
-97
lines changed

7 files changed

+341
-97
lines changed

src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { sendEvent } from '@app/utils/analytics/events'
3838
import { getReadableError } from '@app/utils/errors'
3939
import { getIsCachedData } from '@app/utils/getIsCachedData'
4040
import { useQuery } from '@app/utils/query/useQuery'
41+
import { computeCacheBustFlags } from '@app/utils/transactionCacheBust'
4142
import { hasParaConnection, makeEtherscanLink } from '@app/utils/utils'
4243

4344
import { DisplayItems } from '../DisplayItems'
@@ -456,6 +457,8 @@ export const TransactionStageModal = ({
456457
addRecentTransaction,
457458
dispatch,
458459
isSafeApp,
460+
transactionFlowData: transaction.data,
461+
computeCacheBustFn: computeCacheBustFlags,
459462
}),
460463
},
461464
})

src/components/@molecules/TransactionDialogManager/stage/query.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { call, estimateGas, getTransaction, prepareTransactionRequest } from 'vi
66
import { useConnections } from 'wagmi'
77

88
import { SupportedChain } from '@app/constants/chains'
9-
import { TransactionStatus } from '@app/hooks/transactions/transactionStore'
9+
import { CacheBust, TransactionStatus } from '@app/hooks/transactions/transactionStore'
1010
import { useAddRecentTransaction } from '@app/hooks/transactions/useAddRecentTransaction'
1111
import { useIsSafeApp } from '@app/hooks/useIsSafeApp'
1212
import { createTransactionRequest, TransactionName } from '@app/transaction-flow/transaction'
@@ -50,6 +50,8 @@ export const transactionSuccessHandler =
5050
addRecentTransaction,
5151
dispatch,
5252
isSafeApp,
53+
transactionFlowData,
54+
computeCacheBustFn,
5355
}: {
5456
client: ClientWithEns
5557
connectorClient: ConnectorClientWithEns
@@ -59,6 +61,8 @@ export const transactionSuccessHandler =
5961
addRecentTransaction: ReturnType<typeof useAddRecentTransaction>
6062
dispatch: Dispatch<TransactionFlowAction>
6163
isSafeApp: ReturnType<typeof useIsSafeApp>['data']
64+
transactionFlowData?: unknown
65+
computeCacheBustFn?: (action: string, data: unknown) => CacheBust | undefined
6266
}) =>
6367
async (tx: SendTransactionReturnType) => {
6468
let transactionData: Transaction | null = null
@@ -84,6 +88,12 @@ export const transactionSuccessHandler =
8488
}
8589
}
8690

91+
// Compute cache bust flags from the original flow data
92+
const cacheBust =
93+
transactionFlowData && computeCacheBustFn
94+
? computeCacheBustFn(actionName, transactionFlowData)
95+
: undefined
96+
8797
addRecentTransaction({
8898
...transactionData,
8999
hash: tx,
@@ -93,6 +103,7 @@ export const transactionSuccessHandler =
93103
timestamp: Math.floor(Date.now() / 1000),
94104
isSafeTx: !!isSafeApp,
95105
searchRetries: 0,
106+
cacheBust,
96107
})
97108
dispatch({ name: 'setTransactionHash', payload: { hash: tx, key: txKey! } })
98109
}

src/components/TransactionNotifications.tsx

Lines changed: 29 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useQueryClient } from '@tanstack/react-query'
2-
import { useCallback, useEffect, useRef, useState } from 'react'
2+
import { useCallback, useEffect, useState } from 'react'
33
import { useTranslation } from 'react-i18next'
44
import styled, { css } from 'styled-components'
55
import { useClient } from 'wagmi'
@@ -43,120 +43,53 @@ export const TransactionNotifications = () => {
4343

4444
const [open, setOpen] = useState(false)
4545

46-
const { resumeTransactionFlow, getResumable, getLatestTransaction } = useTransactionFlow()
46+
const { resumeTransactionFlow, getResumable } = useTransactionFlow()
4747

4848
const [notificationQueue, setNotificationQueue] = useState<Notification[]>([])
4949
const currentNotification = notificationQueue[0]
5050

51-
// Use ref to avoid recreating callback when state.items changes
52-
// This prevents cascading re-renders that can restart gas estimation
53-
const getLatestTransactionRef = useRef(getLatestTransaction)
54-
useEffect(() => {
55-
getLatestTransactionRef.current = getLatestTransaction
56-
}, [getLatestTransaction])
57-
5851
const updateCallback = useCallback<UpdateCallback>(
59-
({ action, key, status, hash }) => {
52+
({ action, key, status, hash, cacheBust }) => {
6053
if (status === 'pending' || status === 'repriced') return
6154
if (status === 'confirmed') {
62-
// Get transaction data for cache busting (use ref to avoid dependency)
63-
const latestTx = key ? getLatestTransactionRef.current(key) : undefined
64-
const txData = latestTx?.data as { name?: string; records?: unknown } | undefined
65-
const ensName = txData?.name
66-
55+
// Handle analytics events
6756
switch (action) {
68-
case 'registerName': {
57+
case 'registerName':
6958
trackEvent('register', chainName)
70-
// Bust cache for avatar/header if being set during registration
71-
if (ensName && client) {
72-
const registrationData = txData as {
73-
name: string
74-
records?: { texts?: Array<{ key: string }> }
75-
}
76-
const hasAvatarChange = registrationData.records?.texts?.some(
77-
(text) => text.key === 'avatar',
78-
)
79-
const hasHeaderChange = registrationData.records?.texts?.some(
80-
(text) => text.key === 'header',
81-
)
82-
if (hasAvatarChange) bustMediaCache(ensName, client as ClientWithEns, 'avatar')
83-
if (hasHeaderChange) bustMediaCache(ensName, client as ClientWithEns, 'header')
84-
}
85-
queryClient.invalidateQueries({ queryKey: [META_DATA_QUERY_KEY] })
8659
break
87-
}
8860
case 'commitName':
8961
trackEvent('commit', chainName)
9062
break
9163
case 'extendNames':
9264
trackEvent('renew', chainName)
9365
break
94-
case 'updateResolver':
95-
case 'resetProfile': {
96-
// These actions always bust both avatar and header
97-
if (ensName && client) {
98-
bustMediaCache(ensName, client as ClientWithEns)
99-
}
100-
queryClient.invalidateQueries({ queryKey: [META_DATA_QUERY_KEY] })
101-
break
102-
}
103-
case 'updateProfile':
104-
case 'updateProfileRecords': {
105-
// Check if avatar or header are being modified in records
106-
if (ensName && client) {
107-
const profileData = txData as {
108-
name: string
109-
records?:
110-
| { texts?: Array<{ key: string }> }
111-
| Array<{ key: string; group?: string }>
112-
}
113-
// Handle both RecordOptions format and ProfileRecord[] format
114-
const { records } = profileData
115-
let hasAvatarChange = false
116-
let hasHeaderChange = false
117-
if (Array.isArray(records)) {
118-
// ProfileRecord[] format (updateProfileRecords)
119-
hasAvatarChange = records.some(
120-
(r) => r.key === 'avatar' && (r as { group?: string }).group === 'media',
121-
)
122-
hasHeaderChange = records.some(
123-
(r) => r.key === 'header' && (r as { group?: string }).group === 'media',
124-
)
125-
} else if (records?.texts) {
126-
// RecordOptions format (updateProfile)
127-
hasAvatarChange = records.texts.some((text) => text.key === 'avatar')
128-
hasHeaderChange = records.texts.some((text) => text.key === 'header')
129-
}
130-
if (hasAvatarChange) bustMediaCache(ensName, client as ClientWithEns, 'avatar')
131-
if (hasHeaderChange) bustMediaCache(ensName, client as ClientWithEns, 'header')
132-
}
133-
queryClient.invalidateQueries({ queryKey: [META_DATA_QUERY_KEY] })
134-
break
135-
}
136-
case 'resetProfileWithRecords':
137-
case 'migrateProfile':
138-
case 'migrateProfileWithReset': {
139-
// Check if avatar or header are in the records being set
140-
if (ensName && client) {
141-
const recordsData = txData as {
142-
name: string
143-
records?: { texts?: Array<{ key: string }> }
144-
}
145-
const hasAvatarChange = recordsData.records?.texts?.some(
146-
(text) => text.key === 'avatar',
147-
)
148-
const hasHeaderChange = recordsData.records?.texts?.some(
149-
(text) => text.key === 'header',
150-
)
151-
if (hasAvatarChange) bustMediaCache(ensName, client as ClientWithEns, 'avatar')
152-
if (hasHeaderChange) bustMediaCache(ensName, client as ClientWithEns, 'header')
153-
}
154-
queryClient.invalidateQueries({ queryKey: [META_DATA_QUERY_KEY] })
155-
break
156-
}
15766
default:
15867
break
15968
}
69+
70+
// Use pre-computed cache bust flags from transaction store
71+
if (cacheBust && client) {
72+
const { avatar, header, name } = cacheBust
73+
if (name) {
74+
if (avatar) bustMediaCache(name, client as ClientWithEns, 'avatar')
75+
if (header) bustMediaCache(name, client as ClientWithEns, 'header')
76+
}
77+
}
78+
79+
// Invalidate metadata queries for actions that modify profile data
80+
const profileActions = [
81+
'registerName',
82+
'updateResolver',
83+
'resetProfile',
84+
'updateProfile',
85+
'updateProfileRecords',
86+
'resetProfileWithRecords',
87+
'migrateProfile',
88+
'migrateProfileWithReset',
89+
]
90+
if (profileActions.includes(action)) {
91+
queryClient.invalidateQueries({ queryKey: [META_DATA_QUERY_KEY] })
92+
}
16093
}
16194
const resumable = key && getResumable(key)
16295
const item = {

src/hooks/transactions/transactionStore.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export type TransactionStatus =
2121
| 'unknown'
2222
| 'searching'
2323

24+
export type CacheBust = {
25+
avatar?: boolean
26+
header?: boolean
27+
name?: string
28+
}
29+
2430
interface BaseTransaction {
2531
hash: Hash
2632
action: string
@@ -35,6 +41,7 @@ interface BaseTransaction {
3541
searchStatus?: 'searching' | 'found'
3642
input?: Hex
3743
timestamp?: number
44+
cacheBust?: CacheBust
3845
}
3946

4047
interface SearchingTransaction extends BaseTransaction {
@@ -91,6 +98,7 @@ export type Transaction =
9198
export type NewTransaction = Omit<Transaction, 'status' | 'minedData'> & {
9299
input?: Hex
93100
timestamp?: number
101+
cacheBust?: CacheBust
94102
}
95103

96104
type Data = Record<string, Record<number, Transaction[] | undefined>>

0 commit comments

Comments
 (0)