Skip to content

Commit 1c88b4e

Browse files
committed
fix: very bad performance when scrolling paginated tables
1 parent e3dfcba commit 1c88b4e

File tree

4 files changed

+30
-19
lines changed

4 files changed

+30
-19
lines changed

src/components/PaginatedTable/PaginatedTable.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ export const PaginatedTable = <T, F>({
6969
const tableRef = React.useRef<HTMLDivElement>(null);
7070

7171
const activeChunks = useScrollBasedChunks({
72-
containerRef: parentRef ?? tableRef,
72+
parentRef,
73+
tableRef,
7374
totalItems: foundEntities,
74-
itemHeight: rowHeight,
75+
rowHeight,
7576
chunkSize: limit,
7677
});
7778

src/components/PaginatedTable/TableChunk.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {ResponseError} from '../Errors/ResponseError';
88

99
import {EmptyTableRow, LoadingTableRow, TableRow} from './TableRow';
1010
import type {Column, FetchData, GetRowClassName, SortParams} from './types';
11+
import {typedMemo} from './utils';
1112

1213
const DEBOUNCE_TIMEOUT = 200;
1314

@@ -29,7 +30,7 @@ interface TableChunkProps<T, F> {
2930
}
3031

3132
// Memoisation prevents chunks rerenders that could cause perfomance issues on big tables
32-
export const TableChunk = <T, F>({
33+
export const TableChunk = typedMemo(function TableChunk<T, F>({
3334
id,
3435
limit,
3536
totalLength,
@@ -43,7 +44,7 @@ export const TableChunk = <T, F>({
4344
renderErrorMessage,
4445
onDataFetched,
4546
isActive,
46-
}: TableChunkProps<T, F>) => {
47+
}: TableChunkProps<T, F>) {
4748
const [isTimeoutActive, setIsTimeoutActive] = React.useState(true);
4849
const [autoRefreshInterval] = useAutoRefreshInterval();
4950

@@ -150,4 +151,4 @@ export const TableChunk = <T, F>({
150151
{renderContent()}
151152
</tbody>
152153
);
153-
};
154+
});
Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,42 @@
11
import React from 'react';
22

3-
import {throttle} from 'lodash';
3+
import {isEqual, throttle} from 'lodash';
44

55
import {getArray} from '../../utils';
66

77
interface UseScrollBasedChunksProps {
8-
containerRef: React.RefObject<HTMLElement>;
8+
parentRef?: React.RefObject<HTMLElement>;
9+
tableRef: React.RefObject<HTMLElement>;
910
totalItems: number;
10-
itemHeight: number;
11+
rowHeight: number;
1112
chunkSize: number;
1213
}
1314

1415
const THROTTLE_DELAY = 100;
1516
const CHUNKS_AHEAD_COUNT = 1;
1617

1718
export const useScrollBasedChunks = ({
18-
containerRef,
19+
parentRef,
20+
tableRef,
1921
totalItems,
20-
itemHeight,
22+
rowHeight,
2123
chunkSize,
2224
}: UseScrollBasedChunksProps): number[] => {
2325
const [activeChunks, setActiveChunks] = React.useState<number[]>(
2426
getArray(1 + CHUNKS_AHEAD_COUNT).map((index) => index),
2527
);
2628

2729
const calculateActiveChunks = React.useCallback(() => {
28-
const container = containerRef.current;
29-
if (!container) {
30+
const container = parentRef?.current;
31+
const table = tableRef.current;
32+
if (!container || !table) {
3033
return;
3134
}
3235

33-
const {scrollTop, clientHeight} = container;
34-
const visibleStartIndex = Math.floor(scrollTop / itemHeight);
36+
const tableScrollTop = Math.max(container.scrollTop - table.offsetTop, 0);
37+
const visibleStartIndex = Math.floor(Math.max(tableScrollTop, 0) / rowHeight);
3538
const visibleEndIndex = Math.min(
36-
Math.ceil((scrollTop + clientHeight) / itemHeight),
39+
Math.ceil((tableScrollTop + container.clientHeight) / rowHeight),
3740
totalItems - 1,
3841
);
3942

@@ -44,16 +47,18 @@ export const useScrollBasedChunks = ({
4447
(index) => startChunk + index,
4548
);
4649

47-
setActiveChunks(newActiveChunks);
48-
}, [chunkSize, containerRef, itemHeight, totalItems]);
50+
if (!isEqual(activeChunks, newActiveChunks)) {
51+
setActiveChunks(newActiveChunks);
52+
}
53+
}, [parentRef, tableRef, rowHeight, totalItems, chunkSize, activeChunks]);
4954

5055
const throttledCalculateActiveChunks = React.useMemo(
5156
() => throttle(calculateActiveChunks, THROTTLE_DELAY),
5257
[calculateActiveChunks],
5358
);
5459

5560
React.useEffect(() => {
56-
const container = containerRef.current;
61+
const container = parentRef?.current;
5762
if (!container) {
5863
return undefined;
5964
}
@@ -63,7 +68,7 @@ export const useScrollBasedChunks = ({
6368
container.removeEventListener('scroll', throttledCalculateActiveChunks);
6469
throttledCalculateActiveChunks.cancel();
6570
};
66-
}, [containerRef, throttledCalculateActiveChunks]);
71+
}, [parentRef, throttledCalculateActiveChunks]);
6772

6873
return activeChunks;
6974
};

src/components/PaginatedTable/utils.ts renamed to src/components/PaginatedTable/utils.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React from 'react';
2+
13
// invoke passed function at most once per animation frame
24
// eslint-disable-next-line @typescript-eslint/no-explicit-any
35
export function rafThrottle<Fn extends (...args: any[]) => any>(fn: Fn) {
@@ -23,3 +25,5 @@ export function rafThrottle<Fn extends (...args: any[]) => any>(fn: Fn) {
2325
export function calculateColumnWidth(newWidth: number, minWidth = 40, maxWidth = Infinity) {
2426
return Math.max(minWidth, Math.min(newWidth, maxWidth));
2527
}
28+
29+
export const typedMemo: <T>(Component: T) => T = React.memo;

0 commit comments

Comments
 (0)