Skip to content

Commit

Permalink
Stream transactions in transactions table to avoid blocking loading (#…
Browse files Browse the repository at this point in the history
…935)

* Stream transactions in transactions table to avoid blocking loading

* Tweak performance

* Remove unneeded loader
  • Loading branch information
jessepinho authored Apr 13, 2024
1 parent 6577184 commit cd6f987
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 58 deletions.
104 changes: 48 additions & 56 deletions apps/minifront/src/components/dashboard/transaction-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,59 @@ import {
TableHeader,
TableRow,
} from '@penumbra-zone/ui/components/ui/table';
import { Link, LoaderFunction, useLoaderData } from 'react-router-dom';
import { getAllTransactions, TransactionSummary } from '../../fetchers/transactions';
import { throwIfPraxNotConnectedTimeout } from '@penumbra-zone/client';
import { Link } from 'react-router-dom';
import { shorten } from '@penumbra-zone/types/src/string';

export const TxsLoader: LoaderFunction = async (): Promise<TransactionSummary[]> => {
await throwIfPraxNotConnectedTimeout();
return await getAllTransactions();
};
import { useStore } from '../../state';
import { memo, useEffect } from 'react';
import { TransactionSummary } from '../../state/transactions';

export default function TransactionTable() {
const data = useLoaderData() as TransactionSummary[];
const { summaries, loadSummaries } = useStore(store => store.transactions);

useEffect(() => void loadSummaries(), [loadSummaries]);

return (
<>
<div className='flex flex-col gap-[34px] md:hidden'>
{data.map((tx, i) => (
<div key={i} className='flex justify-between gap-4 border-b pb-3'>
<p className='text-center text-base font-bold'>{tx.height}</p>
<p className='text-center text-base font-bold'>{tx.description}</p>
<p className='text-center font-mono text-base'>
<Link to={`/tx/${tx.hash}`}>{shorten(tx.hash, 8)}</Link>
</p>
</div>
<Table className='md:table'>
<TableHeader className='hidden md:table-header-group'>
<TableRow>
<TableHead className='text-center'>Block Height</TableHead>
<TableHead className='text-center'>Description</TableHead>
<TableHead className='text-center'>Transaction Hash</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{summaries.map(summary => (
<Row key={summary.hash} summary={summary} />
))}
</div>
<Table className='hidden md:table'>
<TableHeader>
<TableRow>
<TableHead className='text-center'>Block Height</TableHead>
<TableHead className='text-center'>Description</TableHead>
<TableHead className='text-center'>Transaction Hash</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((tx, i) => (
<TableRow key={i}>
<TableCell>
<p className='text-center text-base font-bold'>{tx.height}</p>
</TableCell>
<TableCell>
<p className='text-center text-base font-bold'>{tx.description}</p>
</TableCell>
<TableCell>
<p className='text-center font-mono text-base'>
<Link to={`/tx/${tx.hash}`}>{shorten(tx.hash, 8)}</Link>
</p>
</TableCell>
<TableCell>
<Link to={`/tx/${tx.hash}`}>
<img
src='./more.svg'
className='size-4 cursor-pointer hover:opacity-50'
alt='More'
/>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
</TableBody>
</Table>
);
}

/**
* Split into a separate component so that we can use `memo`, which prevents
* rows from re-rendering just because other rows have been added.
*/
const Row = memo(({ summary }: { summary: TransactionSummary }) => (
<TableRow>
<TableCell>
<p className='text-center text-base font-bold'>{summary.height}</p>
</TableCell>
<TableCell>
<p className='text-center text-base font-bold'>{summary.description}</p>
</TableCell>
<TableCell>
<p className='text-center font-mono text-base'>
<Link to={`/tx/${summary.hash}`}>{shorten(summary.hash, 8)}</Link>
</p>
</TableCell>
<TableCell className='hidden md:table-cell'>
<Link to={`/tx/${summary.hash}`}>
<img src='./more.svg' className='size-4 cursor-pointer hover:opacity-50' alt='More' />
</Link>
</TableCell>
</TableRow>
));

Row.displayName = 'Row';
3 changes: 1 addition & 2 deletions apps/minifront/src/components/root-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createHashRouter, redirect } from 'react-router-dom';
import { PagePath } from './metadata/paths';
import { Layout, LayoutLoader } from './layout';
import AssetsTable, { AssetsLoader } from './dashboard/assets-table';
import TransactionTable, { TxsLoader } from './dashboard/transaction-table';
import TransactionTable from './dashboard/transaction-table';
import { DashboardLayout } from './dashboard/layout';
import { TxDetails, TxDetailsErrorBoundary, TxDetailsLoader } from './tx-details';
import { SendLayout } from './send/layout';
Expand Down Expand Up @@ -35,7 +35,6 @@ export const rootRouter = createHashRouter([
},
{
path: PagePath.TRANSACTIONS,
loader: TxsLoader,
element: <TransactionTable />,
errorElement: <ErrorBoundary />,
},
Expand Down
3 changes: 3 additions & 0 deletions apps/minifront/src/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createIbcSendSlice, IbcSendSlice } from './ibc';
import { createSendSlice, SendSlice } from './send';
import { createStakingSlice, StakingSlice } from './staking';
import { createUnclaimedSwapsSlice, UnclaimedSwapsSlice } from './unclaimed-swaps';
import { createTransactionsSlice, TransactionsSlice } from './transactions';

/**
* Required to enable use of `Map`s in Zustand state when using Immer
Expand All @@ -19,6 +20,7 @@ export interface AllSlices {
send: SendSlice;
staking: StakingSlice;
swap: SwapSlice;
transactions: TransactionsSlice;
unclaimedSwaps: UnclaimedSwapsSlice;
}

Expand All @@ -35,6 +37,7 @@ export const initializeStore = () => {
send: createSendSlice()(setState, getState, store),
staking: createStakingSlice()(setState, getState, store),
swap: createSwapSlice()(setState, getState, store),
transactions: createTransactionsSlice()(setState, getState, store),
unclaimedSwaps: createUnclaimedSwapsSlice()(setState, getState, store),
}));
};
Expand Down
41 changes: 41 additions & 0 deletions apps/minifront/src/state/transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { uint8ArrayToHex } from '@penumbra-zone/types/src/hex';
import { SliceCreator } from '.';
import { viewClient } from '../clients';
import { getTransactionClassificationLabel } from '@penumbra-zone/perspective/transaction/classify';

export interface TransactionSummary {
height: number;
hash: string;
description: string;
}

export interface TransactionsSlice {
summaries: TransactionSummary[];
loadSummaries: () => Promise<void>;
}

export const createTransactionsSlice = (): SliceCreator<TransactionsSlice> => (set, get) => ({
summaries: [],

loadSummaries: async () => {
set(state => {
state.transactions.summaries = [];
});

for await (const tx of viewClient.transactionInfo({})) {
const summary = {
height: Number(tx.txInfo?.height ?? 0n),
hash: tx.txInfo?.id?.inner ? uint8ArrayToHex(tx.txInfo.id.inner) : 'unknown',
description: getTransactionClassificationLabel(tx.txInfo?.view),
};

const summaries = [...get().transactions.summaries, summary].sort(
(a, b) => b.height - a.height,
);

set(state => {
state.transactions.summaries = summaries;
});
}
},
});

0 comments on commit cd6f987

Please sign in to comment.