Skip to content

Commit c0d745a

Browse files
authored
fix(AnalyticalTable - subcomponents): fix non-breaking React error & improve performance (#7366)
Fixes #6755
1 parent 90df2c0 commit c0d745a

File tree

3 files changed

+78
-96
lines changed

3 files changed

+78
-96
lines changed
Lines changed: 74 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import type { VirtualItem } from '@tanstack/react-virtual';
2+
import { useIsomorphicLayoutEffect } from '@ui5/webcomponents-react-base';
3+
import { useRef } from 'react';
24
import type { ReactNode } from 'react';
3-
import { useEffect, useRef } from 'react';
4-
import type { ClassNames } from '../types/index.js';
5+
import type { ClassNames, RowType } from '../types/index.js';
56

6-
interface RowSubComponent {
7-
subComponentsHeight: Record<string, { rowId: string; subComponentHeight?: number }>;
7+
interface RowSubComponentProps {
8+
subComponentsHeight: Record<string, { rowId: string; subComponentHeight?: number }> | undefined;
89
virtualRow: VirtualItem;
910
dispatch: (e: { type: string; payload?: Record<string, unknown> }) => void;
10-
row: Record<string, unknown>;
11+
row: RowType;
1112
rowHeight: number;
1213
children: ReactNode | ReactNode[];
13-
rows: Record<string, unknown>[];
14+
rows: RowType[];
1415
alwaysShowSubComponent: boolean;
1516
rowIndex: number;
1617
classNames: ClassNames;
18+
renderSubComp: boolean;
1719
}
1820

19-
export const RowSubComponent = (props: RowSubComponent) => {
21+
export function RowSubComponent(props: RowSubComponentProps) {
2022
const {
21-
subComponentsHeight,
23+
subComponentsHeight = {},
2224
virtualRow,
2325
dispatch,
2426
row,
@@ -28,79 +30,76 @@ export const RowSubComponent = (props: RowSubComponent) => {
2830
alwaysShowSubComponent,
2931
rowIndex,
3032
classNames,
33+
renderSubComp,
3134
} = props;
32-
const subComponentRef = useRef(null);
35+
const subComponentRef = useRef<HTMLDivElement>(null);
3336

34-
useEffect(() => {
35-
const subComponentHeightObserver = new ResizeObserver((entries) => {
36-
entries.forEach((entry) => {
37-
const target = entry.target.getBoundingClientRect();
38-
if (target) {
39-
// Firefox implements `borderBoxSize` as a single content rect, rather than an array
40-
const borderBoxSize = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize;
41-
// Safari doesn't implement `borderBoxSize`
42-
const subCompHeight = borderBoxSize?.blockSize ?? target.height;
43-
if (subComponentsHeight?.[virtualRow.index]?.subComponentHeight !== subCompHeight && subCompHeight !== 0) {
44-
// use most common sub-component height of first 10 sub-components as default height
45-
if (alwaysShowSubComponent && subComponentsHeight && Object.keys(subComponentsHeight).length === 10) {
46-
const objGroupedByHeight = Object.values(subComponentsHeight).reduce((acc, cur) => {
47-
const count = acc?.[cur.subComponentHeight];
48-
if (typeof count === 'number') {
49-
return { ...acc, [cur.subComponentHeight]: count + 1 };
50-
}
51-
return { ...acc, [cur.subComponentHeight]: 1 };
52-
}, {});
37+
useIsomorphicLayoutEffect(() => {
38+
const subComponentElement = subComponentRef.current;
39+
if (!subComponentElement || !renderSubComp) {
40+
return;
41+
}
5342

54-
const mostUsedHeight = Object.keys(objGroupedByHeight).reduce((a, b) =>
55-
objGroupedByHeight[a] > objGroupedByHeight[b] ? a : b,
56-
);
57-
const estimatedHeights = rows.reduce((acc, cur, index) => {
58-
acc[index] = { subComponentHeight: parseInt(mostUsedHeight), rowId: cur.id };
59-
return acc;
60-
}, {});
61-
dispatch({
62-
type: 'SUB_COMPONENTS_HEIGHT',
63-
payload: { ...estimatedHeights, ...subComponentsHeight },
64-
});
65-
} else {
66-
dispatch({
67-
type: 'SUB_COMPONENTS_HEIGHT',
68-
payload: {
69-
...subComponentsHeight,
70-
[virtualRow.index]: { subComponentHeight: subCompHeight, rowId: row.id },
71-
},
72-
});
73-
}
74-
}
75-
// recalc if row id of row index has changed
76-
if (
77-
subComponentsHeight?.[virtualRow.index]?.rowId != null &&
78-
subComponentsHeight?.[virtualRow.index]?.rowId !== row.id
79-
) {
80-
dispatch({
81-
type: 'SUB_COMPONENTS_HEIGHT',
82-
payload: {
83-
...subComponentsHeight,
84-
[virtualRow.index]: { subComponentHeight: subCompHeight, rowId: row.id },
85-
},
86-
});
43+
const measureAndDispatch = (height: number) => {
44+
const prev: { rowId?: string; subComponentHeight?: number } = subComponentsHeight?.[virtualRow.index] ?? {};
45+
if (height === 0 || (prev.subComponentHeight === height && prev.rowId === row.id)) {
46+
return;
47+
}
48+
49+
// use most common subComponentHeight height of first 10 subcomponents as default height
50+
if (alwaysShowSubComponent && Object.keys(subComponentsHeight).length === 10) {
51+
// create frequency map -> most common height has the highest number
52+
const frequencyMap: Record<number, number> = {};
53+
Object.values(subComponentsHeight).forEach(({ subComponentHeight }) => {
54+
if (subComponentHeight) {
55+
frequencyMap[subComponentHeight] = (frequencyMap[subComponentHeight] || 0) + 1;
8756
}
88-
}
89-
});
57+
});
58+
const mostUsedHeight = Number(Object.entries(frequencyMap).sort((a, b) => b[1] - a[1])[0]?.[0] || 0);
59+
const estimatedHeight: typeof subComponentsHeight = {};
60+
rows.forEach((row, index) => {
61+
estimatedHeight[index] = { subComponentHeight: mostUsedHeight, rowId: row.id };
62+
});
63+
dispatch({ type: 'SUB_COMPONENTS_HEIGHT', payload: { ...estimatedHeight, ...subComponentsHeight } });
64+
} else {
65+
dispatch({
66+
type: 'SUB_COMPONENTS_HEIGHT',
67+
payload: {
68+
...subComponentsHeight,
69+
[virtualRow.index]: { subComponentHeight: height, rowId: row.id },
70+
},
71+
});
72+
}
73+
};
74+
75+
measureAndDispatch(subComponentElement.getBoundingClientRect().height);
76+
77+
const observer = new ResizeObserver(([entry]) => {
78+
measureAndDispatch(entry.borderBoxSize[0].blockSize);
9079
});
91-
if (subComponentRef.current?.firstChild) {
92-
subComponentHeightObserver.observe(subComponentRef.current?.firstChild);
93-
}
80+
observer.observe(subComponentElement);
81+
9482
return () => {
95-
subComponentHeightObserver.disconnect();
83+
observer.disconnect();
9684
};
97-
}, [
98-
subComponentRef.current?.firstChild,
99-
subComponentsHeight,
100-
row.id,
101-
subComponentsHeight?.[virtualRow.index]?.subComponentHeight,
102-
virtualRow.index,
103-
]);
85+
}, [renderSubComp, subComponentsHeight, virtualRow.index, row.id, alwaysShowSubComponent, rows]);
86+
87+
// reset subComponentHeight
88+
useIsomorphicLayoutEffect(() => {
89+
if (!renderSubComp && subComponentsHeight?.[virtualRow.index]?.subComponentHeight) {
90+
dispatch({
91+
type: 'SUB_COMPONENTS_HEIGHT',
92+
payload: {
93+
...subComponentsHeight,
94+
[virtualRow.index]: { subComponentHeight: 0, rowId: row.id },
95+
},
96+
});
97+
}
98+
}, [renderSubComp, subComponentsHeight, virtualRow.index, row.id, dispatch]);
99+
100+
if (!renderSubComp) {
101+
return null;
102+
}
104103

105104
return (
106105
<div
@@ -117,6 +116,6 @@ export const RowSubComponent = (props: RowSubComponent) => {
117116
{children}
118117
</div>
119118
);
120-
};
119+
}
121120

122121
RowSubComponent.displayName = 'RowSubComponent';

packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Virtualizer } from '@tanstack/react-virtual';
22
import { clsx } from 'clsx';
33
import type { MutableRefObject } from 'react';
44
import { useEffect, useMemo, useRef } from 'react';
5-
import { AnalyticalTableSubComponentsBehavior } from '../../../enums/index.js';
65
import type {
76
AnalyticalTablePropTypes,
87
ClassNames,
@@ -35,7 +34,6 @@ interface VirtualTableBodyProps {
3534
manualGroupBy?: boolean;
3635
subRowsKey: string;
3736
scrollContainerRef?: MutableRefObject<HTMLDivElement>;
38-
subComponentsBehavior: AnalyticalTablePropTypes['subComponentsBehavior'];
3937
triggerScroll?: TriggerScrollState;
4038
scrollToRef: MutableRefObject<ScrollToRefType>;
4139
rowVirtualizer: Virtualizer<DivWithCustomScrollProp, HTMLElement>;
@@ -62,7 +60,6 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
6260
manualGroupBy,
6361
subRowsKey,
6462
scrollContainerRef,
65-
subComponentsBehavior,
6663
triggerScroll,
6764
rowVirtualizer,
6865
} = props;
@@ -161,21 +158,6 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
161158
const isNavigatedCell = typeof markNavigatedRow === 'function' ? markNavigatedRow(row) : false;
162159
const RowSubComponent = typeof renderRowSubComponent === 'function' ? renderRowSubComponent(row) : undefined;
163160

164-
if (
165-
(!RowSubComponent ||
166-
(subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeightExpandable &&
167-
!row.isExpanded)) &&
168-
subComponentsHeight &&
169-
subComponentsHeight?.[virtualRow.index]?.subComponentHeight
170-
) {
171-
dispatch({
172-
type: 'SUB_COMPONENTS_HEIGHT',
173-
payload: {
174-
...subComponentsHeight,
175-
[virtualRow.index]: { subComponentHeight: 0, rowId: row.id },
176-
},
177-
});
178-
}
179161
let updatedHeight = rowHeight;
180162
if (
181163
renderRowSubComponent &&
@@ -205,7 +187,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
205187
height: `${updatedHeight}px`,
206188
}}
207189
>
208-
{RowSubComponent && (row.isExpanded || alwaysShowSubComponent) && (
190+
{typeof renderRowSubComponent === 'function' && (
209191
<SubComponent
210192
subComponentsHeight={subComponentsHeight}
211193
virtualRow={virtualRow}
@@ -216,6 +198,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
216198
alwaysShowSubComponent={alwaysShowSubComponent}
217199
rowIndex={visibleRowIndex + 1}
218200
classNames={classes}
201+
renderSubComp={RowSubComponent && (row.isExpanded || alwaysShowSubComponent)}
219202
>
220203
{RowSubComponent}
221204
</SubComponent>

packages/main/src/components/AnalyticalTable/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -549,12 +549,13 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
549549
adjustTableHeightOnPopIn
550550
? popInRowHeight
551551
: internalRowHeight;
552+
552553
if (includeSubCompRowHeight) {
553554
let initialBodyHeightWithSubComps = 0;
554555
for (let i = 0; i < rowNum; i++) {
555556
if (tableState.subComponentsHeight[i]) {
556557
initialBodyHeightWithSubComps += tableState.subComponentsHeight[i].subComponentHeight + rowHeight;
557-
} else if (rows[i]) {
558+
} else {
558559
initialBodyHeightWithSubComps += rowHeight;
559560
}
560561
}
@@ -870,7 +871,6 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
870871
columnVirtualizer={columnVirtualizer}
871872
manualGroupBy={reactTableOptions?.manualGroupBy as boolean | undefined}
872873
subRowsKey={subRowsKey}
873-
subComponentsBehavior={subComponentsBehavior}
874874
triggerScroll={tableState.triggerScroll}
875875
rowVirtualizer={rowVirtualizer}
876876
/>

0 commit comments

Comments
 (0)