Commit a4fb335
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-e69cee057ead1 parent 8d14a4d commit a4fb335
1 file changed
Lines changed: 3 additions & 3 deletions
Lines changed: 3 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
0 commit comments