Skip to content

Commit 5ecaecb

Browse files
authored
fix: nodes list stops working on sort by uptime scroll down (#1424)
1 parent 1c786cb commit 5ecaecb

15 files changed

+118
-132
lines changed

src/components/PaginatedTable/PaginatedTable.tsx

+16-27
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {
1919
RenderErrorMessage,
2020
SortParams,
2121
} from './types';
22-
import {useIntersectionObserver} from './useIntersectionObserver';
22+
import {useScrollBasedChunks} from './useScrollBasedChunks';
2323

2424
import './PaginatedTable.scss';
2525

@@ -32,7 +32,7 @@ export interface PaginatedTableProps<T, F> {
3232
columns: Column<T>[];
3333
getRowClassName?: GetRowClassName<T>;
3434
rowHeight?: number;
35-
parentContainer?: Element | null;
35+
parentRef?: React.RefObject<HTMLElement>;
3636
initialSortParams?: SortParams;
3737
onColumnsResize?: HandleTableColumnsResize;
3838
renderControls?: RenderControls;
@@ -50,7 +50,7 @@ export const PaginatedTable = <T, F>({
5050
columns,
5151
getRowClassName,
5252
rowHeight = DEFAULT_TABLE_ROW_HEIGHT,
53-
parentContainer,
53+
parentRef,
5454
initialSortParams,
5555
onColumnsResize,
5656
renderControls,
@@ -64,46 +64,36 @@ export const PaginatedTable = <T, F>({
6464
const [sortParams, setSortParams] = React.useState<SortParams | undefined>(initialSortParams);
6565
const [totalEntities, setTotalEntities] = React.useState(initialTotal);
6666
const [foundEntities, setFoundEntities] = React.useState(initialFound);
67-
const [activeChunks, setActiveChunks] = React.useState<number[]>([]);
6867
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
6968

70-
const tableContainer = React.useRef<HTMLDivElement>(null);
69+
const tableRef = React.useRef<HTMLDivElement>(null);
70+
71+
const activeChunks = useScrollBasedChunks({
72+
containerRef: parentRef ?? tableRef,
73+
totalItems: foundEntities,
74+
itemHeight: rowHeight,
75+
chunkSize: limit,
76+
});
7177

7278
const handleDataFetched = React.useCallback((total: number, found: number) => {
7379
setTotalEntities(total);
7480
setFoundEntities(found);
7581
setIsInitialLoad(false);
7682
}, []);
7783

78-
const onEntry = React.useCallback((id: string) => {
79-
setActiveChunks((prev) => [...new Set([...prev, Number(id)])]);
80-
}, []);
81-
82-
const onLeave = React.useCallback((id: string) => {
83-
setActiveChunks((prev) => prev.filter((chunk) => chunk !== Number(id)));
84-
}, []);
85-
86-
const observer = useIntersectionObserver({onEntry, onLeave, parentContainer});
87-
8884
// reset table on filters change
8985
React.useLayoutEffect(() => {
9086
setTotalEntities(initialTotal);
9187
setFoundEntities(initialFound);
9288
setIsInitialLoad(true);
93-
if (parentContainer) {
94-
parentContainer.scrollTo(0, 0);
89+
if (parentRef?.current) {
90+
parentRef.current.scrollTo(0, 0);
9591
} else {
96-
tableContainer.current?.scrollTo(0, 0);
92+
tableRef.current?.scrollTo(0, 0);
9793
}
98-
99-
setActiveChunks([0]);
100-
}, [filters, initialFound, initialTotal, limit, parentContainer]);
94+
}, [filters, initialFound, initialTotal, limit, parentRef]);
10195

10296
const renderChunks = () => {
103-
if (!observer) {
104-
return null;
105-
}
106-
10797
if (!isInitialLoad && foundEntities === 0) {
10898
return (
10999
<tbody>
@@ -133,7 +123,6 @@ export const PaginatedTable = <T, F>({
133123
renderErrorMessage={renderErrorMessage}
134124
onDataFetched={handleDataFetched}
135125
isActive={activeChunks.includes(value)}
136-
observer={observer}
137126
/>
138127
));
139128
};
@@ -161,7 +150,7 @@ export const PaginatedTable = <T, F>({
161150
};
162151

163152
return (
164-
<div ref={tableContainer} className={b(null, containerClassName)}>
153+
<div ref={tableRef} className={b(null, containerClassName)}>
165154
{renderContent()}
166155
</div>
167156
);

src/components/PaginatedTable/TableChunk.tsx

+3-23
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import type {Column, FetchData, GetRowClassName, SortParams} from './types';
1111

1212
const DEBOUNCE_TIMEOUT = 200;
1313

14-
// With original memo generic types are lost
15-
const typedMemo: <T>(Component: T) => T = React.memo;
16-
1714
interface TableChunkProps<T, F> {
1815
id: number;
1916
limit: number;
@@ -22,7 +19,6 @@ interface TableChunkProps<T, F> {
2219
columns: Column<T>[];
2320
filters?: F;
2421
sortParams?: SortParams;
25-
observer: IntersectionObserver;
2622
isActive: boolean;
2723
tableName: string;
2824

@@ -33,7 +29,7 @@ interface TableChunkProps<T, F> {
3329
}
3430

3531
// Memoisation prevents chunks rerenders that could cause perfomance issues on big tables
36-
export const TableChunk = typedMemo(function TableChunk<T, F>({
32+
export const TableChunk = <T, F>({
3733
id,
3834
limit,
3935
totalLength,
@@ -43,13 +39,11 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
4339
tableName,
4440
filters,
4541
sortParams,
46-
observer,
4742
getRowClassName,
4843
renderErrorMessage,
4944
onDataFetched,
5045
isActive,
51-
}: TableChunkProps<T, F>) {
52-
const ref = React.useRef<HTMLTableSectionElement>(null);
46+
}: TableChunkProps<T, F>) => {
5347
const [isTimeoutActive, setIsTimeoutActive] = React.useState(true);
5448
const [autoRefreshInterval] = useAutoRefreshInterval();
5549

@@ -83,19 +77,6 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
8377
};
8478
}, [isActive, isTimeoutActive]);
8579

86-
React.useEffect(() => {
87-
const el = ref.current;
88-
if (el) {
89-
observer.observe(el);
90-
}
91-
92-
return () => {
93-
if (el) {
94-
observer.unobserve(el);
95-
}
96-
};
97-
}, [observer]);
98-
9980
React.useEffect(() => {
10081
if (currentData && isActive) {
10182
const {total = 0, found = 0} = currentData;
@@ -154,7 +135,6 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
154135

155136
return (
156137
<tbody
157-
ref={ref}
158138
id={id.toString()}
159139
style={{
160140
height: `${chunkHeight}px`,
@@ -167,4 +147,4 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
167147
{renderContent()}
168148
</tbody>
169149
);
170-
});
150+
};

src/components/PaginatedTable/useIntersectionObserver.ts

-43
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React from 'react';
2+
3+
import {throttle} from 'lodash';
4+
5+
import {getArray} from '../../utils';
6+
7+
interface UseScrollBasedChunksProps {
8+
containerRef: React.RefObject<HTMLElement>;
9+
totalItems: number;
10+
itemHeight: number;
11+
chunkSize: number;
12+
}
13+
14+
const THROTTLE_DELAY = 100;
15+
const CHUNKS_AHEAD_COUNT = 1;
16+
17+
export const useScrollBasedChunks = ({
18+
containerRef,
19+
totalItems,
20+
itemHeight,
21+
chunkSize,
22+
}: UseScrollBasedChunksProps): number[] => {
23+
const [activeChunks, setActiveChunks] = React.useState<number[]>(
24+
getArray(1 + CHUNKS_AHEAD_COUNT).map((index) => index),
25+
);
26+
27+
const calculateActiveChunks = React.useCallback(() => {
28+
const container = containerRef.current;
29+
if (!container) {
30+
return;
31+
}
32+
33+
const {scrollTop, clientHeight} = container;
34+
const visibleStartIndex = Math.floor(scrollTop / itemHeight);
35+
const visibleEndIndex = Math.min(
36+
Math.ceil((scrollTop + clientHeight) / itemHeight),
37+
totalItems - 1,
38+
);
39+
40+
const startChunk = Math.floor(visibleStartIndex / chunkSize);
41+
const endChunk = Math.floor(visibleEndIndex / chunkSize);
42+
43+
const newActiveChunks = getArray(endChunk - startChunk + 1 + CHUNKS_AHEAD_COUNT).map(
44+
(index) => startChunk + index,
45+
);
46+
47+
setActiveChunks(newActiveChunks);
48+
}, [chunkSize, containerRef, itemHeight, totalItems]);
49+
50+
const throttledCalculateActiveChunks = React.useMemo(
51+
() => throttle(calculateActiveChunks, THROTTLE_DELAY),
52+
[calculateActiveChunks],
53+
);
54+
55+
React.useEffect(() => {
56+
const container = containerRef.current;
57+
if (!container) {
58+
return undefined;
59+
}
60+
61+
container.addEventListener('scroll', throttledCalculateActiveChunks);
62+
return () => {
63+
container.removeEventListener('scroll', throttledCalculateActiveChunks);
64+
throttledCalculateActiveChunks.cancel();
65+
};
66+
}, [containerRef, throttledCalculateActiveChunks]);
67+
68+
return activeChunks;
69+
};

src/containers/Cluster/Cluster.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export function Cluster({
164164
path={getLocationObjectFromHref(getClusterPath(clusterTabsIds.nodes)).pathname}
165165
>
166166
<NodesWrapper
167-
parentContainer={container.current}
167+
parentRef={container}
168168
additionalNodesProps={additionalNodesProps}
169169
/>
170170
</Route>
@@ -173,7 +173,7 @@ export function Cluster({
173173
getLocationObjectFromHref(getClusterPath(clusterTabsIds.storage)).pathname
174174
}
175175
>
176-
<StorageWrapper parentContainer={container.current} />
176+
<StorageWrapper parentRef={container} />
177177
</Route>
178178
<Route
179179
path={

src/containers/Node/Node.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export function Node(props: NodeProps) {
127127
case STORAGE: {
128128
return (
129129
<div className={b('storage')}>
130-
<StorageWrapper nodeId={nodeId} parentContainer={container.current} />
130+
<StorageWrapper nodeId={nodeId} parentRef={container} />
131131
</div>
132132
);
133133
}

src/containers/Nodes/NodesWrapper.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import {PaginatedNodes} from './PaginatedNodes';
88
interface NodesWrapperProps {
99
path?: string;
1010
database?: string;
11-
parentContainer?: Element | null;
11+
parentRef?: React.RefObject<HTMLElement>;
1212
additionalNodesProps?: AdditionalNodesProps;
1313
}
1414

15-
export const NodesWrapper = ({parentContainer, ...props}: NodesWrapperProps) => {
15+
export const NodesWrapper = ({parentRef, ...props}: NodesWrapperProps) => {
1616
const [usePaginatedTables] = useSetting<boolean>(USE_PAGINATED_TABLES_KEY);
1717

1818
if (usePaginatedTables) {
19-
return <PaginatedNodes parentContainer={parentContainer} {...props} />;
19+
return <PaginatedNodes parentRef={parentRef} {...props} />;
2020
}
2121

2222
return <Nodes {...props} />;

src/containers/Nodes/PaginatedNodes.tsx

+3-8
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,11 @@ const b = cn('ydb-nodes');
4444
interface NodesProps {
4545
path?: string;
4646
database?: string;
47-
parentContainer?: Element | null;
47+
parentRef?: React.RefObject<HTMLElement>;
4848
additionalNodesProps?: AdditionalNodesProps;
4949
}
5050

51-
export const PaginatedNodes = ({
52-
path,
53-
database,
54-
parentContainer,
55-
additionalNodesProps,
56-
}: NodesProps) => {
51+
export const PaginatedNodes = ({path, database, parentRef, additionalNodesProps}: NodesProps) => {
5752
const [queryParams, setQueryParams] = useQueryParams({
5853
uptimeFilter: StringParam,
5954
search: StringParam,
@@ -140,7 +135,7 @@ export const PaginatedNodes = ({
140135
return (
141136
<ResizeablePaginatedTable
142137
columnsWidthLSKey={NODES_COLUMNS_WIDTH_LS_KEY}
143-
parentContainer={parentContainer}
138+
parentRef={parentRef}
144139
columns={columnsToShow}
145140
fetchData={getNodes}
146141
limit={50}

src/containers/Storage/PaginatedStorage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface PaginatedStorageProps {
1212

1313
viewContext: StorageViewContext;
1414

15-
parentContainer?: Element | null;
15+
parentRef?: React.RefObject<HTMLElement>;
1616

1717
initialEntitiesCount?: number;
1818
}

src/containers/Storage/PaginatedStorageGroups.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function StorageGroupsComponent({
5555
groupId,
5656
pDiskId,
5757
viewContext,
58-
parentContainer,
58+
parentRef,
5959
initialEntitiesCount,
6060
}: PaginatedStorageProps) {
6161
const {searchValue, visibleEntities, handleShowAllGroups} = useStorageQueryParams();
@@ -88,7 +88,7 @@ function StorageGroupsComponent({
8888
searchValue={searchValue}
8989
visibleEntities={visibleEntities}
9090
onShowAll={handleShowAllGroups}
91-
parentContainer={parentContainer}
91+
parentRef={parentRef}
9292
renderControls={renderControls}
9393
renderErrorMessage={renderPaginatedTableErrorMessage}
9494
columns={columnsToShow}

0 commit comments

Comments
 (0)