Skip to content

Commit a4fb335

Browse files
authored
fix(AnalyticalTable): prevent empty table body flash on first render (#8375)
## Description When an `AnalyticalTable` with `visibleRowCountMode="Auto"` mounts, there is a brief but visible flash where the table headers and toolbar are fully rendered while the body area is empty. After a frame the rows appear, but the flicker is noticeable — especially in applications where the table is rendered inside a layout that itself triggers re-layouts on mount (e.g. an `ObjectPage` with `IconTabBar` mode). ### Root cause `VirtualTableBodyContainer` needs the body `<div>` to be in the DOM before the virtualizer can measure it and render rows. To enforce this, it gates the row children behind an `isMounted` flag: ```tsx const [isMounted, setIsMounted] = useState(false); useEffect(() => { if (parentRef.current) { setIsMounted(true); } }, [parentRef]); // later in JSX: {isMounted && children} ``` The problem is the choice of `useEffect`. React's `useEffect` runs **after** the browser paints, which means every mount follows this sequence: 1. React commits the component tree to the DOM — the body `<div ref={parentRef}>` is in the document, but `isMounted` is still `false`, so `children` (the rows) are not part of the tree. 2. The **browser paints** this state — the user sees a complete table frame with an empty body. 3. Only now does `useEffect` fire, finding `parentRef.current` set and calling `setIsMounted(true)`. 4. React re-renders with the rows included, the browser paints again — the table is now complete. Step 2 is the flash. It is a single frame, but it is consistently reproducible on every mount. ### Fix Replace `useEffect` with `useLayoutEffect`. Because `useLayoutEffect` runs synchronously after DOM mutations but **before** the browser paints, the ref check and state update happen in time for React to include the rows in the very first paint. The user never sees an empty body. ### Why this is safe - `parentRef.current` is guaranteed to be available when the layout effect runs — the `<div ref={parentRef}>` renders unconditionally in the same component's JSX, so the ref is assigned during the commit phase before any effects fire. - The `AnalyticalTable` itself already uses `useIsomorphicLayoutEffect` (which resolves to `useLayoutEffect` on the client) in its main `index.tsx` for similar DOM-measurement work, so this is consistent with the existing codebase. - This is exactly the use case described in the [React docs on `useEffect`](https://react.dev/reference/react/useEffect#my-effect-does-something-visual-and-i-see-a-flicker-before-it-runs): *"If your Effect is doing something visual (for example, positioning a tooltip), and the delay is noticeable (for example, it flickers), replace `useEffect` with `useLayoutEffect`."* ### Result **Before** — empty body flashes for one frame on every mount: https://github.com/user-attachments/assets/c527cd74-54c1-42a0-bbe1-c8719c385f04 **After** — rows appear together with headers, no flash: https://github.com/user-attachments/assets/f1953e9f-03e3-48a4-b591-e69cee057ead
1 parent 8d14a4d commit a4fb335

1 file changed

Lines changed: 3 additions & 3 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base';
1+
import { enrichEventWithDetails, useIsomorphicLayoutEffect } from '@ui5/webcomponents-react-base';
22
import { clsx } from 'clsx';
33
import type { MutableRefObject } from 'react';
44
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -46,9 +46,9 @@ export const VirtualTableBodyContainer = (props: VirtualTableBodyContainerProps)
4646
} = props;
4747
const [isMounted, setIsMounted] = useState(false);
4848

49-
useEffect(() => {
49+
useIsomorphicLayoutEffect(() => {
5050
if (parentRef.current) {
51-
// Trigger one-time re-render after first render -> safe to set state here
51+
// Run before paint so rows appear with the body container (avoids empty-body flash).
5252
// eslint-disable-next-line react-hooks/set-state-in-effect
5353
setIsMounted(true);
5454
}

0 commit comments

Comments
 (0)