Skip to content

Commit b86a4f0

Browse files
committed
fix: chunks logics
1 parent e89ee94 commit b86a4f0

File tree

2 files changed

+212
-85
lines changed

2 files changed

+212
-85
lines changed

src/components/PaginatedTable/PaginatedTable.tsx

Lines changed: 21 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

33
import {usePaginatedTableState} from './PaginatedTableContext';
4-
import {TableChunk} from './TableChunk';
4+
import {TableChunksRenderer} from './TableChunksRenderer';
55
import {TableHead} from './TableHead';
66
import {DEFAULT_TABLE_ROW_HEIGHT} from './constants';
77
import {b} from './shared';
@@ -14,7 +14,6 @@ import type {
1414
RenderEmptyDataMessage,
1515
RenderErrorMessage,
1616
} from './types';
17-
import {useScrollBasedChunks} from './useScrollBasedChunks';
1817
import {calculateElementOffsetTop} from './utils';
1918

2019
import './PaginatedTable.scss';
@@ -65,31 +64,13 @@ export const PaginatedTable = <T, F>({
6564
const tableRef = React.useRef<HTMLDivElement>(null);
6665
const [tableOffset, setTableOffset] = React.useState(0);
6766

68-
const chunkStates = useScrollBasedChunks({
69-
scrollContainerRef,
70-
tableRef,
71-
totalItems: foundEntities,
72-
rowHeight,
73-
chunkSize,
74-
tableOffset,
75-
});
76-
7767
// this prevent situation when filters are new, but active chunks is not yet recalculated (it will be done to the next rendrer, so we bring filters change on the next render too)
7868
const [filters, setFilters] = React.useState(rawFilters);
7969

8070
React.useEffect(() => {
8171
setFilters(rawFilters);
8272
}, [rawFilters]);
8373

84-
const lastChunkSize = React.useMemo(() => {
85-
// If foundEntities = 0, there will only first chunk
86-
// Display it with 1 row, to display empty data message
87-
if (!foundEntities) {
88-
return 1;
89-
}
90-
return foundEntities % chunkSize || chunkSize;
91-
}, [foundEntities, chunkSize]);
92-
9374
const handleDataFetched = React.useCallback(
9475
(data?: PaginatedTableData<T>) => {
9576
if (data) {
@@ -131,74 +112,29 @@ export const PaginatedTable = <T, F>({
131112
setIsInitialLoad(true);
132113
}, [initialEntitiesCount, setTotalEntities, setFoundEntities, setIsInitialLoad]);
133114

134-
const renderChunks = () => {
135-
const chunks: React.ReactElement[] = [];
136-
let i = 0;
137-
138-
while (i < chunkStates.length) {
139-
const chunkState = chunkStates[i];
140-
const shouldRender = chunkState.shouldRender;
141-
const shouldFetch = chunkState.shouldFetch;
142-
const isActive = shouldRender || shouldFetch;
143-
144-
if (isActive) {
145-
chunks.push(
146-
<TableChunk<T, F>
147-
key={i}
148-
id={i}
149-
calculatedCount={i === chunkStates.length - 1 ? lastChunkSize : chunkSize}
150-
chunkSize={chunkSize}
151-
rowHeight={rowHeight}
152-
columns={columns}
153-
fetchData={fetchData}
154-
filters={filters}
155-
tableName={tableName}
156-
sortParams={sortParams}
157-
getRowClassName={getRowClassName}
158-
renderErrorMessage={renderErrorMessage}
159-
renderEmptyDataMessage={renderEmptyDataMessage}
160-
onDataFetched={handleDataFetched}
161-
shouldFetch={chunkState.shouldFetch}
162-
shouldRender={chunkState.shouldRender}
163-
keepCache={keepCache}
164-
/>,
165-
);
166-
}
167-
168-
if (shouldRender) {
169-
i++;
170-
} else {
171-
// Find consecutive inactive chunks and merge them
172-
const startIndex = i;
173-
let totalHeight = 0;
174-
175-
while (i < chunkStates.length && !chunkStates[i].shouldRender) {
176-
const currentChunkSize =
177-
i === chunkStates.length - 1 ? lastChunkSize : chunkSize;
178-
totalHeight += currentChunkSize * rowHeight;
179-
i++;
180-
}
181-
182-
// Render merged separator for consecutive inactive chunks
183-
chunks.push(
184-
<tr
185-
style={{height: `${totalHeight}px`}}
186-
className="separator"
187-
key={`separator-${startIndex}-${i - 1}`}
188-
>
189-
<td colSpan={columns.length} style={{padding: 0, border: 'none'}} />
190-
</tr>,
191-
);
192-
}
193-
}
194-
195-
return chunks;
196-
};
197-
198115
const renderTable = () => (
199116
<table className={b('table')}>
200117
<TableHead columns={columns} onSort={setSortParams} onColumnsResize={onColumnsResize} />
201-
<tbody>{renderChunks()}</tbody>
118+
<tbody>
119+
<TableChunksRenderer
120+
scrollContainerRef={scrollContainerRef}
121+
tableRef={tableRef}
122+
foundEntities={foundEntities}
123+
tableOffset={tableOffset}
124+
chunkSize={chunkSize}
125+
rowHeight={rowHeight}
126+
columns={columns}
127+
fetchData={fetchData}
128+
filters={filters}
129+
tableName={tableName}
130+
sortParams={sortParams}
131+
getRowClassName={getRowClassName}
132+
renderErrorMessage={renderErrorMessage}
133+
renderEmptyDataMessage={renderEmptyDataMessage}
134+
onDataFetched={handleDataFetched}
135+
keepCache={keepCache}
136+
/>
137+
</tbody>
202138
</table>
203139
);
204140

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import React from 'react';
2+
3+
import {TableChunk} from './TableChunk';
4+
import {b} from './shared';
5+
import type {
6+
Column,
7+
FetchData,
8+
GetRowClassName,
9+
PaginatedTableData,
10+
RenderEmptyDataMessage,
11+
RenderErrorMessage,
12+
SortParams,
13+
} from './types';
14+
import {useScrollBasedChunks} from './useScrollBasedChunks';
15+
16+
export interface TableChunksRendererProps<T, F> {
17+
scrollContainerRef: React.RefObject<HTMLElement>;
18+
tableRef: React.RefObject<HTMLElement>;
19+
foundEntities: number;
20+
tableOffset: number;
21+
chunkSize: number;
22+
rowHeight: number;
23+
columns: Column<T>[];
24+
fetchData: FetchData<T, F>;
25+
filters?: F;
26+
tableName: string;
27+
sortParams?: SortParams;
28+
getRowClassName?: GetRowClassName<T>;
29+
renderErrorMessage?: RenderErrorMessage;
30+
renderEmptyDataMessage?: RenderEmptyDataMessage;
31+
onDataFetched: (data?: PaginatedTableData<T>) => void;
32+
keepCache: boolean;
33+
}
34+
35+
export const TableChunksRenderer = <T, F>({
36+
scrollContainerRef,
37+
tableRef,
38+
foundEntities,
39+
tableOffset,
40+
chunkSize,
41+
rowHeight,
42+
columns,
43+
fetchData,
44+
filters,
45+
tableName,
46+
sortParams,
47+
getRowClassName,
48+
renderErrorMessage,
49+
renderEmptyDataMessage,
50+
onDataFetched,
51+
keepCache,
52+
}: TableChunksRendererProps<T, F>) => {
53+
const chunkStates = useScrollBasedChunks({
54+
scrollContainerRef,
55+
tableRef,
56+
totalItems: foundEntities || 1,
57+
rowHeight,
58+
chunkSize,
59+
tableOffset,
60+
});
61+
62+
const lastChunkSize = React.useMemo(() => {
63+
// If foundEntities = 0, there will only first chunk
64+
// Display it with 1 row, to display empty data message
65+
if (!foundEntities) {
66+
return 1;
67+
}
68+
return foundEntities % chunkSize || chunkSize;
69+
}, [foundEntities, chunkSize]);
70+
71+
const findRenderChunkRange = React.useCallback(() => {
72+
const firstRenderIndex = chunkStates.findIndex((state) => state.shouldRender);
73+
const lastRenderIndex = chunkStates.findLastIndex((state) => state.shouldRender);
74+
return {firstRenderIndex, lastRenderIndex};
75+
}, [chunkStates]);
76+
77+
const findFetchChunkRange = React.useCallback(() => {
78+
const firstFetchIndex = chunkStates.findIndex((state) => state.shouldFetch);
79+
const lastFetchIndex = chunkStates.findLastIndex((state) => state.shouldFetch);
80+
return {firstFetchIndex, lastFetchIndex};
81+
}, [chunkStates]);
82+
83+
const calculateSeparatorHeight = React.useCallback(
84+
(startIndex: number, endIndex: number) => {
85+
let totalHeight = 0;
86+
for (let i = startIndex; i < endIndex; i++) {
87+
const currentChunkSize = i === chunkStates.length - 1 ? lastChunkSize : chunkSize;
88+
totalHeight += currentChunkSize * rowHeight;
89+
}
90+
return totalHeight;
91+
},
92+
[chunkSize, chunkStates.length, lastChunkSize, rowHeight],
93+
);
94+
95+
const createSeparator = React.useCallback(
96+
(startIndex: number, endIndex: number, key: string) => {
97+
const height = calculateSeparatorHeight(startIndex, endIndex);
98+
return (
99+
<tr style={{height: `${height}px`}} className={b(key)} key={key}>
100+
<td colSpan={columns.length} style={{padding: 0, border: 'none'}} />
101+
</tr>
102+
);
103+
},
104+
[calculateSeparatorHeight, columns.length],
105+
);
106+
107+
const createChunk = React.useCallback(
108+
(chunkIndex: number) => {
109+
const chunkState = chunkStates[chunkIndex];
110+
return (
111+
<TableChunk<T, F>
112+
key={chunkIndex}
113+
id={chunkIndex}
114+
calculatedCount={
115+
chunkIndex === chunkStates.length - 1 ? lastChunkSize : chunkSize
116+
}
117+
chunkSize={chunkSize}
118+
rowHeight={rowHeight}
119+
columns={columns}
120+
fetchData={fetchData}
121+
filters={filters}
122+
tableName={tableName}
123+
sortParams={sortParams}
124+
getRowClassName={getRowClassName}
125+
renderErrorMessage={renderErrorMessage}
126+
renderEmptyDataMessage={renderEmptyDataMessage}
127+
onDataFetched={onDataFetched}
128+
shouldFetch={chunkState.shouldFetch}
129+
shouldRender={chunkState.shouldRender}
130+
keepCache={keepCache}
131+
/>
132+
);
133+
},
134+
[
135+
chunkSize,
136+
chunkStates,
137+
columns,
138+
fetchData,
139+
filters,
140+
getRowClassName,
141+
keepCache,
142+
lastChunkSize,
143+
onDataFetched,
144+
renderEmptyDataMessage,
145+
renderErrorMessage,
146+
rowHeight,
147+
sortParams,
148+
tableName,
149+
],
150+
);
151+
152+
const renderChunks = React.useCallback(() => {
153+
// Chunk states are distrubuted like [fetch, fetch, render/fetch, render/fetch, fetch, fetch]
154+
// i.e. fetched chunks include rendered chunks
155+
const {firstFetchIndex, lastFetchIndex} = findFetchChunkRange();
156+
const {firstRenderIndex, lastRenderIndex} = findRenderChunkRange();
157+
const elements: React.ReactElement[] = [];
158+
159+
// No fetch chunks found
160+
if (firstFetchIndex === -1) {
161+
return elements;
162+
}
163+
164+
// Beginning separator (for chunks before first render chunk)
165+
if (firstRenderIndex > 0) {
166+
elements.push(createSeparator(0, firstRenderIndex, 'separator-beginning'));
167+
}
168+
169+
// All fetch chunks (shouldFetch = true) get rendered as TableChunk components
170+
for (let i = firstFetchIndex; i <= lastFetchIndex; i++) {
171+
elements.push(createChunk(i));
172+
}
173+
174+
// End separator (for chunks after last render chunk)
175+
if (lastRenderIndex < chunkStates.length - 1) {
176+
elements.push(
177+
createSeparator(lastRenderIndex + 1, chunkStates.length, 'separator-end'),
178+
);
179+
}
180+
181+
return elements;
182+
}, [
183+
chunkStates.length,
184+
createChunk,
185+
createSeparator,
186+
findFetchChunkRange,
187+
findRenderChunkRange,
188+
]);
189+
190+
return <React.Fragment>{renderChunks()}</React.Fragment>;
191+
};

0 commit comments

Comments
 (0)