Skip to content

Commit 711e0f6

Browse files
authored
Merge pull request #14 from dsaltares/feat/13-fx-perf
feat: 13 - improve exchange rate table performance
2 parents e0fd322 + 21987f6 commit 711e0f6

File tree

3 files changed

+116
-104
lines changed

3 files changed

+116
-104
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import type { Row } from '@tanstack/react-table';
2+
import { flexRender } from '@tanstack/react-table';
3+
import { forwardRef, useCallback } from 'react';
4+
import Paper from '@mui/material/Paper';
5+
import TableContainer from '@mui/material/TableContainer';
6+
import Table from '@mui/material/Table';
7+
import TableBody from '@mui/material/TableBody';
8+
import TableHead from '@mui/material/TableHead';
9+
import TableRow from '@mui/material/TableRow';
10+
import TableCell from '@mui/material/TableCell';
11+
import TableSortLabel from '@mui/material/TableSortLabel';
12+
import { TableVirtuoso, type TableComponents } from 'react-virtuoso';
13+
import useSortFromUrl from '@lib/useSortFromUrl';
14+
import type { ExchangeRate } from '@server/exchangeRates/types';
15+
import useExchangeRatesTable, { DefaultSort } from './useExchangeRatesTable';
16+
17+
const VirtuosoTableComponents: TableComponents<Row<ExchangeRate>> = {
18+
Scroller: forwardRef<HTMLDivElement>((props, ref) => (
19+
<TableContainer component={Paper} {...props} ref={ref} />
20+
)),
21+
Table: (props) => (
22+
<Table
23+
{...props}
24+
size="small"
25+
sx={{ borderCollapse: 'separate', tableLayout: 'fixed', minWidth: 650 }}
26+
/>
27+
),
28+
TableHead: forwardRef<HTMLTableSectionElement>((props, ref) => (
29+
<TableHead {...props} ref={ref} sx={{ backgroundColor: 'white' }} />
30+
)),
31+
TableRow: ({ item: _item, ...props }) => <TableRow {...props} hover />,
32+
TableBody: forwardRef<HTMLTableSectionElement>((props, ref) => (
33+
<TableBody {...props} ref={ref} />
34+
)),
35+
};
36+
37+
type Props = {
38+
rates: ExchangeRate[];
39+
onUpdateRate: (idx: number, rate: ExchangeRate) => void;
40+
};
41+
42+
export default function ExchangeRatesTableTable({
43+
rates,
44+
onUpdateRate,
45+
}: Props) {
46+
const table = useExchangeRatesTable(rates, onUpdateRate);
47+
const { toggleSort } = useSortFromUrl(DefaultSort);
48+
49+
const fixedHeaderContent = useCallback(
50+
() =>
51+
table.getHeaderGroups().map((headerGroup) => (
52+
<TableRow key={headerGroup.id}>
53+
{headerGroup.headers.map((header) => (
54+
<TableCell
55+
key={header.id}
56+
sortDirection={header.column.getIsSorted()}
57+
align={
58+
header.getContext().column.columnDef.meta?.numeric
59+
? 'right'
60+
: 'left'
61+
}
62+
>
63+
{header.column.columnDef.enableSorting !== false ? (
64+
<TableSortLabel
65+
active={!!header.column.getIsSorted()}
66+
direction={header.column.getIsSorted() || undefined}
67+
onClick={() => toggleSort(header.column.id)}
68+
>
69+
{flexRender(
70+
header.column.columnDef.header,
71+
header.getContext(),
72+
)}
73+
</TableSortLabel>
74+
) : (
75+
flexRender(header.column.columnDef.header, header.getContext())
76+
)}
77+
</TableCell>
78+
))}
79+
</TableRow>
80+
)),
81+
[table, toggleSort],
82+
);
83+
84+
return (
85+
<Paper sx={{ height: '100%' }}>
86+
<TableVirtuoso
87+
data={table.getRowModel().rows}
88+
components={VirtuosoTableComponents}
89+
fixedHeaderContent={fixedHeaderContent}
90+
itemContent={rowContent}
91+
/>
92+
</Paper>
93+
);
94+
}
95+
96+
function rowContent(_index: number, row: Row<ExchangeRate>) {
97+
return (
98+
<>
99+
{row.getVisibleCells().map((cell) => (
100+
<TableCell
101+
key={cell.id}
102+
align={cell.column.columnDef.meta?.numeric ? 'right' : 'left'}
103+
>
104+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
105+
</TableCell>
106+
))}
107+
</>
108+
);
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ExchangeRatesTable';
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,36 @@
1-
import Avatar from '@mui/material/Avatar';
2-
import type { HeaderGroup, Row } from '@tanstack/react-table';
1+
import { useMemo } from 'react';
32
import {
43
createColumnHelper,
5-
flexRender,
64
getCoreRowModel,
75
getFilteredRowModel,
86
getSortedRowModel,
97
useReactTable,
108
} from '@tanstack/react-table';
9+
import Avatar from '@mui/material/Avatar';
1110
import stringToColor from 'string-to-color';
12-
import { memo, useMemo } from 'react';
1311
import Typography from '@mui/material/Typography';
14-
import Paper from '@mui/material/Paper';
15-
import TableContainer from '@mui/material/TableContainer';
16-
import Table from '@mui/material/Table';
17-
import TableBody from '@mui/material/TableBody';
18-
import TableHead from '@mui/material/TableHead';
19-
import TableRow from '@mui/material/TableRow';
20-
import TableCell from '@mui/material/TableCell';
21-
import TableSortLabel from '@mui/material/TableSortLabel';
2212
import TextField from '@mui/material/TextField';
2313
import InputAdornment from '@mui/material/InputAdornment';
24-
import flags from '@lib/flags';
2514
import useFiltersFromUrl from '@lib/useFiltersFromUrl';
2615
import useSortFromUrl from '@lib/useSortFromUrl';
2716
import type { ExchangeRate } from '@server/exchangeRates/types';
17+
import flags from '@lib/flags';
2818
import { formatDate } from '@lib/format';
2919

3020
export const DefaultSort = { id: 'ticker', desc: true };
31-
3221
const columnHelper = createColumnHelper<ExchangeRate>();
33-
const ExchangeRateTableHead = memo(ExchangeRateTableHeadBase);
34-
const ExchangeRateTableRow = memo(ExchangeRateTableRowBase);
35-
36-
type Props = {
37-
rates: ExchangeRate[];
38-
onUpdateRate: (idx: number, rate: ExchangeRate) => void;
39-
};
40-
41-
export default function ExchangeRatesTableTable({
42-
rates,
43-
onUpdateRate,
44-
}: Props) {
45-
const table = useExchangeRatesTable(rates, onUpdateRate);
46-
47-
return (
48-
<Paper>
49-
<TableContainer>
50-
<Table size="small">
51-
<ExchangeRateTableHead headerGroups={table.getHeaderGroups()} />
52-
<TableBody>
53-
{table.getRowModel().rows.map((row) => (
54-
<ExchangeRateTableRow key={row.id} row={row} />
55-
))}
56-
</TableBody>
57-
</Table>
58-
</TableContainer>
59-
</Paper>
60-
);
61-
}
6222

63-
function useExchangeRatesTable(
23+
export default function useExchangeRatesTable(
6424
rates: ExchangeRate[],
65-
onUpdateRate: Props['onUpdateRate'],
25+
onUpdateRate: (idx: number, rate: ExchangeRate) => void,
6626
) {
6727
const { sorting } = useSortFromUrl(DefaultSort);
6828
const { filtersByField } = useFiltersFromUrl();
6929

7030
const columns = useMemo(
7131
() => [
7232
columnHelper.display({
33+
enableSorting: false,
7334
id: 'avatar',
7435
header: '',
7536
cell: (info) => (
@@ -151,62 +112,3 @@ function useExchangeRatesTable(
151112

152113
return table;
153114
}
154-
155-
type ExchangeRateTableHeadProps = {
156-
headerGroups: HeaderGroup<ExchangeRate>[];
157-
};
158-
159-
function ExchangeRateTableHeadBase({
160-
headerGroups,
161-
}: ExchangeRateTableHeadProps) {
162-
const { toggleSort } = useSortFromUrl(DefaultSort);
163-
return (
164-
<TableHead>
165-
{headerGroups.map((headerGroup) => (
166-
<TableRow key={headerGroup.id}>
167-
{headerGroup.headers.map((header) => (
168-
<TableCell
169-
key={header.id}
170-
sortDirection={header.column.getIsSorted()}
171-
align={
172-
header.getContext().column.columnDef.meta?.numeric
173-
? 'right'
174-
: 'left'
175-
}
176-
>
177-
<TableSortLabel
178-
active={!!header.column.getIsSorted()}
179-
direction={header.column.getIsSorted() || undefined}
180-
onClick={() => toggleSort(header.column.id)}
181-
>
182-
{flexRender(
183-
header.column.columnDef.header,
184-
header.getContext(),
185-
)}
186-
</TableSortLabel>
187-
</TableCell>
188-
))}
189-
</TableRow>
190-
))}
191-
</TableHead>
192-
);
193-
}
194-
195-
type ExchangeRateTableRowProps = {
196-
row: Row<ExchangeRate>;
197-
};
198-
199-
function ExchangeRateTableRowBase({ row }: ExchangeRateTableRowProps) {
200-
return (
201-
<TableRow>
202-
{row.getVisibleCells().map((cell) => (
203-
<TableCell
204-
key={cell.id}
205-
align={cell.column.columnDef.meta?.numeric ? 'right' : 'left'}
206-
>
207-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
208-
</TableCell>
209-
))}
210-
</TableRow>
211-
);
212-
}

0 commit comments

Comments
 (0)