Skip to content

Commit 3ca7dbd

Browse files
authored
docs(AnalyticalTable): add FAQ with dev mode performance guide (#8278)
Related to: #8234
1 parent 531b08b commit 3ca7dbd

17 files changed

Lines changed: 192 additions & 46 deletions

.storybook/components/Redirect.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import '@ui5/webcomponents-fiori/dist/illustrations/PageNotFound.js';
2+
import { IllustratedMessage, Link, Text } from '@ui5/webcomponents-react';
3+
import { useEffect, useState } from 'react';
4+
5+
interface RedirectProps {
6+
from: string;
7+
to: string;
8+
sectionName: string;
9+
/**
10+
* Delay in ms before redirecting
11+
* @default 5000
12+
*/
13+
delay?: number;
14+
}
15+
16+
export const Redirect = ({ from, to, sectionName, delay = 1000000 }: RedirectProps) => {
17+
const [countdown, setCountdown] = useState(Math.ceil(delay / 1000));
18+
19+
const target = typeof window !== 'undefined' ? window.top || window.parent || window : null;
20+
const currentHref = target?.location.href ?? '';
21+
const newHref = currentHref.replace(from, to);
22+
23+
useEffect(() => {
24+
if (typeof window === 'undefined') return;
25+
if (!currentHref.includes(from)) return;
26+
27+
const timer = setTimeout(() => {
28+
target.location.replace(newHref);
29+
}, delay);
30+
31+
const countdownInterval = setInterval(() => {
32+
setCountdown((prev) => Math.max(0, prev - 1));
33+
}, 1000);
34+
35+
return () => {
36+
clearTimeout(timer);
37+
clearInterval(countdownInterval);
38+
};
39+
}, [from, to, delay, currentHref, newHref, target]);
40+
41+
return (
42+
<IllustratedMessage
43+
name="PageNotFound"
44+
titleText="Page Moved"
45+
subtitle={
46+
<Text>
47+
This page has been moved to <strong>{sectionName}</strong>.
48+
<br />
49+
Redirecting in {countdown} seconds...
50+
</Text>
51+
}
52+
>
53+
<Link href={newHref} target="_top">
54+
Go to {sectionName}
55+
</Link>
56+
</IllustratedMessage>
57+
);
58+
};

.storybook/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './TableOfContent.js';
88
export * from './LabelWithWrapping.js';
99
export * from './CommandsAndQueries.js';
1010
export * from './FilterBarExample.js';
11+
export * from './Redirect.js';

.storybook/manager.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ addons.setConfig({
5656
showRoots: true,
5757
filters: {
5858
patterns: (item) => {
59+
// todo: remove once "AnalyticalTable / Recipes" section is removed.
60+
if (item.id?.includes('analyticaltable') && item.title.includes('Recipes')) {
61+
return false;
62+
}
5963
return !item.tags.includes('excludeFromSidebar');
6064
},
6165
},

packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx renamed to packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.mdx

File renamed without changes.

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx renamed to packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.stories.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,25 @@ import {
1717
FlexBoxDirection,
1818
FlexBoxJustifyContent,
1919
TextAlign,
20-
} from '../../enums/index.js';
21-
import { Button } from '../../webComponents/Button/index.js';
22-
import { IllustratedMessage } from '../../webComponents/IllustratedMessage/index.js';
23-
import { Label } from '../../webComponents/Label/index.js';
24-
import { MultiComboBox } from '../../webComponents/MultiComboBox/index.js';
25-
import { MultiComboBoxItem } from '../../webComponents/MultiComboBoxItem/index.js';
26-
import { Option } from '../../webComponents/Option/index.js';
27-
import type { SegmentedButtonPropTypes } from '../../webComponents/SegmentedButton/index.js';
28-
import { SegmentedButton } from '../../webComponents/SegmentedButton/index.js';
29-
import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/index.js';
30-
import { Select } from '../../webComponents/Select/index.js';
31-
import { Tag } from '../../webComponents/Tag/index.js';
32-
import { Text } from '../../webComponents/Text/index.js';
33-
import type { ToggleButtonPropTypes } from '../../webComponents/ToggleButton/index.js';
34-
import { ToggleButton } from '../../webComponents/ToggleButton/index.js';
35-
import { FlexBox } from '../FlexBox/index.js';
36-
import { ObjectStatus } from '../ObjectStatus/index.js';
37-
import type { AnalyticalTableColumnDefinition, AnalyticalTablePropTypes } from './index.js';
38-
import { AnalyticalTable } from './index.js';
20+
} from '../../../enums/index.js';
21+
import { Button } from '../../../webComponents/Button/index.js';
22+
import { IllustratedMessage } from '../../../webComponents/IllustratedMessage/index.js';
23+
import { Label } from '../../../webComponents/Label/index.js';
24+
import { MultiComboBox } from '../../../webComponents/MultiComboBox/index.js';
25+
import { MultiComboBoxItem } from '../../../webComponents/MultiComboBoxItem/index.js';
26+
import { Option } from '../../../webComponents/Option/index.js';
27+
import type { SegmentedButtonPropTypes } from '../../../webComponents/SegmentedButton/index.js';
28+
import { SegmentedButton } from '../../../webComponents/SegmentedButton/index.js';
29+
import { SegmentedButtonItem } from '../../../webComponents/SegmentedButtonItem/index.js';
30+
import { Select } from '../../../webComponents/Select/index.js';
31+
import { Tag } from '../../../webComponents/Tag/index.js';
32+
import { Text } from '../../../webComponents/Text/index.js';
33+
import type { ToggleButtonPropTypes } from '../../../webComponents/ToggleButton/index.js';
34+
import { ToggleButton } from '../../../webComponents/ToggleButton/index.js';
35+
import { FlexBox } from '../../FlexBox/index.js';
36+
import { ObjectStatus } from '../../ObjectStatus/index.js';
37+
import type { AnalyticalTableColumnDefinition, AnalyticalTablePropTypes } from '../index.js';
38+
import { AnalyticalTable } from '../index.js';
3939

4040
const kitchenSinkArgs: AnalyticalTablePropTypes = {
4141
data: dataLarge,

packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.mdx renamed to packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.mdx

File renamed without changes.

packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx renamed to packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.stories.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
66
import InputType from '@ui5/webcomponents/dist/types/InputType.js';
77
import paperPlaneIcon from '@ui5/webcomponents-icons/dist/paper-plane';
88
import { useCallback, useMemo, useReducer, useState } from 'react';
9-
import { AnalyticalTableSelectionMode, FlexBoxAlignItems, FlexBoxDirection } from '../../enums';
10-
import { Button } from '../../webComponents/Button/index.js';
11-
import { CheckBox } from '../../webComponents/CheckBox/index.js';
12-
import type { InputDomRef } from '../../webComponents/Input/index.js';
13-
import { Input } from '../../webComponents/Input/index.js';
14-
import { Label } from '../../webComponents/Label/index.js';
15-
import { Switch } from '../../webComponents/Switch/index.js';
16-
import { Tag } from '../../webComponents/Tag/index.js';
17-
import { Text } from '../../webComponents/Text/index.js';
18-
import { ToggleButton } from '../../webComponents/ToggleButton/index.js';
19-
import { FlexBox } from '../FlexBox';
20-
import meta from './AnalyticalTable.stories';
21-
import * as AnalyticalTableHooks from './pluginHooks/AnalyticalTableHooks';
22-
import { useF2CellEdit } from './pluginHooks/AnalyticalTableHooks';
23-
import { AnalyticalTable } from './index';
24-
import type { AnalyticalTableCellInstance, AnalyticalTableColumnDefinition } from './index';
9+
import { AnalyticalTableSelectionMode, FlexBoxAlignItems, FlexBoxDirection } from '../../../enums/index.js';
10+
import { Button } from '../../../webComponents/Button/index.js';
11+
import { CheckBox } from '../../../webComponents/CheckBox/index.js';
12+
import type { InputDomRef } from '../../../webComponents/Input/index.js';
13+
import { Input } from '../../../webComponents/Input/index.js';
14+
import { Label } from '../../../webComponents/Label/index.js';
15+
import { Switch } from '../../../webComponents/Switch/index.js';
16+
import { Tag } from '../../../webComponents/Tag/index.js';
17+
import { Text } from '../../../webComponents/Text/index.js';
18+
import { ToggleButton } from '../../../webComponents/ToggleButton/index.js';
19+
import { FlexBox } from '../../FlexBox/index.js';
20+
import { AnalyticalTable } from '../index.js';
21+
import type { AnalyticalTableCellInstance, AnalyticalTableColumnDefinition } from '../index.js';
22+
import * as AnalyticalTableHooks from '../pluginHooks/AnalyticalTableHooks.js';
23+
import { useF2CellEdit } from '../pluginHooks/AnalyticalTableHooks.js';
24+
import meta from './AnalyticalTable.stories.js';
2525

2626
const pluginsMeta = {
2727
...meta,

packages/main/src/components/AnalyticalTable/Recipes.mdx renamed to packages/main/src/components/AnalyticalTable/docs/FAQ.mdx

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,92 @@
11
import { TableOfContent } from '@sb/components';
22
import { Meta, Canvas } from '@storybook/addon-docs/blocks';
33
import { Footer } from '@sb/components';
4-
import { MessageStrip } from '../../webComponents/MessageStrip/index';
4+
import { MessageStrip } from '../../../webComponents/MessageStrip/index.js';
55
import * as ComponentStories from './AnalyticalTable.stories';
66

7-
<Meta title="Data Display / AnalyticalTable / Recipes" />
7+
<Meta title="Data Display / AnalyticalTable / FAQ" />
88

9-
# AnalyticalTable Recipes
9+
# AnalyticalTable FAQ
1010

1111
<TableOfContent />
1212

13+
## Why is the AnalyticalTable slow in development mode?
14+
15+
When using the `AnalyticalTable` (or other virtualized components) in development mode, you may experience noticeable performance degradation compared to production builds. This is expected behavior caused by the combination of **React's dev mode overhead** and **browser debugger instrumentation**.
16+
17+
### Why Dev Mode is Slower
18+
19+
#### React Dev Mode Overhead
20+
21+
React's development build includes significant overhead:
22+
23+
- **Strict Mode double-rendering**: Components, `useEffect`, and callback refs render an additional time to help detect side effects
24+
- **Validation checks**: Hooks order, prop types, deprecated API usage
25+
- **Extended error messages**: Component stack traces for better debugging
26+
- **Extra code paths**: Development-only warnings and diagnostics
27+
28+
For a virtualized table that frequently mounts and unmounts cells during scrolling, this overhead compounds quickly.
29+
30+
#### Browser Debugger Instrumentation
31+
32+
Chromium-based browsers (Chrome, Edge, Brave, etc.) instrument async operations heavily in dev mode via the V8 engine - even when DevTools is closed. Firefox and Safari have significantly lighter debugger overhead in dev mode for example.
33+
34+
### Recommendations
35+
36+
#### High Impact
37+
38+
1. **Use Firefox or Safari for development**: These browsers have significantly lighter debugger overhead in dev mode.
39+
40+
2. **Test with production builds**: When working on performance-sensitive features, test with a production build to get accurate performance metrics.
41+
42+
3. **Disable React Strict Mode** (temporarily): If dev mode performance is severely impacting your workflow, you can temporarily disable Strict Mode in development. Note that Strict Mode helps catch bugs, so re-enable it periodically.
43+
44+
4. **Memoize props that require it**: Props marked with "Must be memoized" (e.g., `columns`) need a stable reference. Define them outside the component or use `useMemo`/`useCallback` to prevent unnecessary recalculations.
45+
46+
#### Medium Impact
47+
48+
5. **Disable React DevTools extension**: The React DevTools browser extension adds performance overhead and has been observed to cause small memory leaks.
49+
50+
6. **Consider memoizing expensive Cell components**: If you have custom `Cell` renderers with expensive computations or deep component trees, wrapping them with `React.memo()` can help by creating render boundaries. However, avoid over-memoization - for simple cells, the overhead of memoization may outweigh the benefits.
51+
52+
7. **Use [direct imports](?path=/docs/knowledge-base-faq--docs#why-use-direct-imports-via-package-export-maps)**: Since tree shaking is not available for most bundlers in dev mode, use direct imports to reduce initial load.
53+
54+
#### Lower Impact
55+
56+
8. **Keep DevTools closed**: When not actively debugging, keep DevTools closed to reduce overhead from source map parsing, DOM mutation observers, and memory tracking.
57+
58+
9. **Deactivate UI5 Web Components animations in dev mode**: Animations cause components to recalculate multiple times. While we debounce frequent state changes, disabling animations can help.
59+
60+
### Summary
61+
62+
Dev mode slowness for virtualized components is expected behavior, not a bug. In production builds, the `AnalyticalTable` performs well. For the best development experience with heavy tables, consider using Firefox or Safari, or testing with production builds when performance is critical.
63+
64+
<details>
65+
<summary>Example: Performance Trace Comparison</summary>
66+
67+
The following table shows a performance trace comparison from an investigation into dev mode performance ([GitHub issue #8234](https://github.com/SAP/ui5-webcomponents-react/issues/8234)). Your results may vary depending on your implementation, but the general pattern holds:
68+
69+
| Metric | Chrome DEV | Chrome PROD | Firefox DEV |
70+
| --------------- | ---------- | ----------- | ----------- |
71+
| Trace file size | 561 MB | 3.3 MB | 6.3 MB |
72+
| Event count | ~millions | ~14,600 | ~3,700 |
73+
74+
Firefox in dev mode has similar overhead to Chrome in prod mode - both are lightweight. Chromium-based browsers in dev mode are the outlier with massive V8 debugger instrumentation.
75+
76+
For more context, see the [detailed findings comment](https://github.com/SAP/ui5-webcomponents-react/issues/8234#issuecomment-3971742068) and [production environment performance videos](https://github.com/SAP/ui5-webcomponents-react/issues/8234#issuecomment-3960465202) showing table performance (inside a complex container) on high-end, medium-range, and low-end devices.
77+
78+
</details>
79+
1380
## How to add your own plugin hooks?
1481

1582
The `AnalyticalTable` internally uses all plugin hooks of the [react-table v7](https://github.com/TanStack/table/blob/v7/docs/src/pages/docs/api/overview.md) except for `usePagination` and hooks that change the layout.
1683
If you pass a `react-table` hook to the `tableHooks` prop of the `AnalyticalTable` you will most likely experience some side effects or even break the table completely. We therefore strongly recommend to just not do it ;).
1784
If you encounter a functionality that should be available with `react-table` hooks, but isn't in the `AnalyticalTable` please open an issue in GitHub.
1885

1986
<MessageStrip
20-
type="Warning"
21-
hideCloseButton
22-
children={`Plugin hooks can manipulate the internal table instance. Please use with caution!`}
87+
type="Warning"
88+
hideCloseButton
89+
children={`Plugin hooks can manipulate the internal table instance. Please use with caution!`}
2390
/>
2491

2592
For adding custom plugin hooks you can use the following code snippet.
@@ -438,8 +505,8 @@ For multi-line content or custom components that don't handle ellipsis, truncati
438505
### Example
439506

440507
<details>
441-
<summary>Show Example</summary>
442-
<Canvas of={ComponentStories.EllipsisExamples} />
508+
<summary>Show Example</summary>
509+
<Canvas of={ComponentStories.EllipsisExamples} />
443510
</details>
444511

445512
<br />

packages/main/src/components/AnalyticalTable/PluginAnnounceEmptyCells.mdx renamed to packages/main/src/components/AnalyticalTable/docs/PluginAnnounceEmptyCells.mdx

File renamed without changes.

packages/main/src/components/AnalyticalTable/PluginDisableRowSelection.mdx renamed to packages/main/src/components/AnalyticalTable/docs/PluginDisableRowSelection.mdx

File renamed without changes.

0 commit comments

Comments
 (0)