diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 1b0acd48..16626544 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -76,6 +76,31 @@ export default function Transactions() { [visibleTransactions, filter] ) + /** + * Memoized data transformer: pre-computes formatted display values for + * every visible row so that expensive operations (toLocaleString, Date + * arithmetic, i18n lookups, string truncation) run only when the source + * data or filter changes, not on every render. + */ + interface TableRow extends Transaction { + formattedAmount: string + formattedTime: string + statusBadgeVariant: string + formattedHash: string + } + + const tableRows: TableRow[] = useMemo( + () => + filtered.map((tx) => ({ + ...tx, + formattedAmount: tx.amountUsdc != null ? formatUsdc(tx.amountUsdc) : '\u2014', + formattedTime: relativeTime(tx.timestamp), + statusBadgeVariant: STATUS_BADGE_MAP[tx.status], + formattedHash: truncateAddress(tx.hash), + })), + [filtered] + ) + // Reset transient overlays when the underlying dataset changes (refetch // or unmount between fetches). Stale IDs would otherwise leak selection // into the new list. @@ -328,11 +353,11 @@ export default function Transactions() { - {filtered.map((tx) => { - const selected = isRowSelected(tx.id) + {tableRows.map((row) => { + const selected = isRowSelected(row.id) return ( @@ -340,31 +365,31 @@ export default function Transactions() { toggleRow(tx.id)} - aria-label={t('transactions.bulk.selectLabel', { id: tx.id })} + onChange={() => toggleRow(row.id)} + aria-label={t('transactions.bulk.selectLabel', { id: row.id })} /> - {tx.type} + {row.type} - {tx.amountUsdc != null ? formatUsdc(tx.amountUsdc) : '—'} + {row.formattedAmount} - + - + - {' '} + {' '} {t('transactions.table.viewLink')}