Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions src/pages/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -328,43 +353,43 @@ export default function Transactions() {
</tr>
</thead>
<tbody>
{filtered.map((tx) => {
const selected = isRowSelected(tx.id)
{tableRows.map((row) => {
const selected = isRowSelected(row.id)
return (
<tr
key={tx.id}
key={row.id}
data-selected={selected ? 'true' : undefined}
className={selected ? 'transactions__row--selected' : undefined}
>
<td data-label="Select" className="transactions__selectCell">
<input
type="checkbox"
checked={selected}
onChange={() => 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 })}
/>
</td>
<td data-label="Type" className="transactions__type">
{tx.type}
{row.type}
</td>
<td data-label="Amount">
{tx.amountUsdc != null ? formatUsdc(tx.amountUsdc) : '—'}
{row.formattedAmount}
</td>
<td data-label="Status">
<Badge variant={STATUS_BADGE_MAP[tx.status]} label={tx.status} />
<Badge variant={row.statusBadgeVariant} label={row.status} />
</td>
<td data-label="When">
<time dateTime={tx.timestamp}>{relativeTime(tx.timestamp)}</time>
<time dateTime={row.timestamp}>{row.formattedTime}</time>
</td>
<td data-label="Transaction">
<AddressDisplay address={tx.hash} className="transactions__hash" />{' '}
<AddressDisplay address={row.hash} className="transactions__hash" />{' '}
<a
href={explorerUrl(network, tx.hash)}
href={explorerUrl(network, row.hash)}
target="_blank"
rel="noopener noreferrer"
className="transactions__explorerLink"
aria-label={t('transactions.table.viewOnExplorer', {
hash: truncateAddress(tx.hash),
hash: row.formattedHash,
})}
>
{t('transactions.table.viewLink')}
Expand Down