From 9b79fae318f3329f77780419924c3b7653e697c4 Mon Sep 17 00:00:00 2001 From: John Kapantzakis Date: Wed, 13 May 2026 12:58:54 +0300 Subject: [PATCH 1/2] Defer measureElement --- src/components/table/body/index.js | 57 ++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/components/table/body/index.js b/src/components/table/body/index.js index 9e36f279..bafe4df3 100644 --- a/src/components/table/body/index.js +++ b/src/components/table/body/index.js @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useRef, useEffect } from "react" +import React, { memo, useCallback, useRef, useEffect, useState } from "react" import { useVirtualizer, defaultRangeExtractor } from "@tanstack/react-virtual" import identity from "lodash/identity" import Flex from "@/components/templates/flex" @@ -35,11 +35,38 @@ const Body = memo( initialOffset = 0, onScroll, enableColumnReordering, + deferMeasureMs, ...rest }) => { useTableState(rerenderSelector) const ref = useRef() + const isDeferEnabled = !!deferMeasureMs && deferMeasureMs > 0 + + const scrollTimeoutRef = useRef(null) + const [measureEnabled, setMeasureEnabled] = useState(true) + + const handleScrollDetection = useCallback(() => { + setMeasureEnabled(false) + + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + } + + scrollTimeoutRef.current = setTimeout(() => { + setMeasureEnabled(true) + }, deferMeasureMs) + }, [deferMeasureMs]) + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current) + } + } + }, []) + const { rows } = table.getRowModel() const rowVirtualizer = useVirtualizer({ @@ -64,6 +91,27 @@ const Body = memo( }, []), }) + // useCallback unconditionally to satisfy React Rules of Hooks. + // When isDeferEnabled is false, behavior is identical to rowVirtualizer.measureElement. + const measureRef = useCallback( + node => { + if (!isDeferEnabled) { + rowVirtualizer.measureElement(node) + return + } + if (measureEnabled && node) { + rowVirtualizer.measureElement(node) + } + }, + [isDeferEnabled, measureEnabled, rowVirtualizer] + ) + + useEffect(() => { + if (isDeferEnabled && measureEnabled) { + rowVirtualizer.measure() + } + }, [isDeferEnabled, measureEnabled, rowVirtualizer]) + if (virtualRef) virtualRef.current = rowVirtualizer const virtualRows = rowVirtualizer.getVirtualItems() @@ -99,7 +147,10 @@ const Body = memo( overflow="auto" flex="1" data-testid={`netdata-table${testPrefix}`} - onScroll={onScroll} + onScroll={e => { + if (isDeferEnabled) handleScrollDetection() + if (onScroll) onScroll(e) + }} >
{virtualRow.index === 0 ? (
Date: Wed, 13 May 2026 13:00:58 +0300 Subject: [PATCH 2/2] Minor change --- src/components/table/body/index.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/table/body/index.js b/src/components/table/body/index.js index bafe4df3..a59485e5 100644 --- a/src/components/table/body/index.js +++ b/src/components/table/body/index.js @@ -91,17 +91,12 @@ const Body = memo( }, []), }) - // useCallback unconditionally to satisfy React Rules of Hooks. - // When isDeferEnabled is false, behavior is identical to rowVirtualizer.measureElement. const measureRef = useCallback( node => { - if (!isDeferEnabled) { + if (!isDeferEnabled || (measureEnabled && node)) { rowVirtualizer.measureElement(node) return } - if (measureEnabled && node) { - rowVirtualizer.measureElement(node) - } }, [isDeferEnabled, measureEnabled, rowVirtualizer] )