Repository: appsmithorg/appsmith
Pull request #39646: feat: implement table widget infinite scroll with dynamic height
Author: login: jacquesikot
Labels: name: Enhancement
description: New feature or request
name: Widgets Product
description: This label groups issues related to widgets
name: Task
description: A simple Todo
name: Table Widget V2
description: Issues related to Table Widget V2
name: ok-to-test
description: Required label for CI
name: Widgets & Accelerators Pod
description: Issues related to widgets & Accelerators
name: Query Widgets & IDE Pod
description: All issues related to Query, JS, Eval, Widgets & IDE
Created: 2025-03-10T08:00:48Z | Merged: 2025-03-20T09:23:37Z

## Description
This PR adds support for variable height rows in the Table Widget when using infinite scroll. The implementation dynamically adjusts row heights based on content, particularly for wrapped text and HTML content.\

## Key Features
1. **Dynamic Row Height Calculation**: Rows automatically resize based on content length when cell wrapping is enabled or HTML content is present
2. **Smooth Infinite Scrolling**: Maintains smooth scrolling performance while supporting variable height rows
3. **Responsive Layout**: Rows adjust in real-time when table data changes or when cell wrapping is toggled

## Implementation Details
The implementation replaces the fixed-size virtual list with a variable-size virtual list that can handle rows of different heights:
1. Created a new `BaseVirtualList` component that uses `VariableSizeList` from react-window
2. Added row height measurement logic in the `Row` component to calculate optimal heights
3. Implemented a context-based system to store and update row heights
4. Created a utility hook `useColumnVariableHeight` to track columns that need variable height handling

## Testing
Added comprehensive Cypress tests that verify:
1. Fixed height behavior when cell wrapping is disabled
2. Increased row heights when cell wrapping is enabled
3. Dynamic height updates when content changes
4. Proper handling of HTML content that have extended heights
5. Reverting to fixed height when wrapping is disabled


Fixes #39089, #39091  

## Automation

/ok-to-test tags="@tag.Widget, @tag.Sanity, @tag.Binding"

### :mag: Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/13946877628>
> Commit: 9f50f22adcb8a8ab9c2aa566c7a0f21e49d1beee
> <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13946877628&attempt=1" target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Widget, @tag.Sanity, @tag.Binding`
> Spec:
> <hr>Wed, 19 Mar 2025 14:39:17 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Enhanced the table widget to automatically adjust row heights based on varying content lengths, wrapping settings, and HTML content for a smoother user experience.
	- Improved performance with optimized virtualization of table rows, ensuring efficient rendering and smooth infinite scrolling.

- **Tests**
	- Added a comprehensive test suite validating the table’s behavior under fixed, dynamic, and updated content conditions, ensuring consistent row height adjustments during user interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #39089: Task: support dynamic height content(variable size list)

Diff:
diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts
new file mode 100644
index 000000000000..b3a1d716add6
--- /dev/null
+++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts
@@ -0,0 +1,173 @@
+import { featureFlagIntercept } from "../../../../../support/Objects/FeatureFlags";
+import { propPane, table } from "../../../../../support/Objects/ObjectsCore";
+
+const DEFAULT_ROW_HEIGHT = 40;
+
+describe(
+  "Table Widget Dynamic Row Height",
+  { tags: ["@tag.Widget", "@tag.Table"] },
+  function () {
+    before(() => {
+      featureFlagIntercept({
+        release_tablev2_infinitescroll_enabled: true,
+      });
+
+      // Set up a table with test data
+      cy.dragAndDropToCanvas("tablewidgetv2", { x: 300, y: 300 });
+
+      // Create test data with varying content lengths
+      const testData = [
+        {
+          id: 1,
+          name: "Short text",
+          description: "This is a short description",
+        },
+        {
+          id: 2,
+          name: "Medium length text",
+          description:
+            "This description is a bit longer and might wrap to the next line depending on column width",
+        },
+        {
+          id: 3,
+          name: "Very long text content",
+          description:
+            "This is a very long description that will definitely wrap to multiple lines when cell wrapping is enabled. It contains enough text to ensure that the row height will need to expand significantly to accommodate all the content properly.",
+        },
+      ];
+
+      // Set the table data
+      propPane.EnterJSContext("Table data", JSON.stringify(testData));
+
+      // Turn on Infinite Scroll
+      propPane.TogglePropertyState("Infinite scroll", "On");
+    });
+
+    it("1. Should maintain fixed height when cell wrapping is disabled and no HTML cells are present", () => {
+      cy.get(".t--widget-tablewidgetv2 .tbody .tr").each(($row) => {
+        cy.wrap($row)
+          .invoke("outerHeight")
+          .then((height) => {
+            if (height !== undefined) {
+              expect(Math.ceil(height)).to.equal(DEFAULT_ROW_HEIGHT);
+            }
+          });
+      });
+    });
+
+    it("2. Should increase row height when cell wrapping is enabled", () => {
+      // turn on cell wrapping
+      table.EditColumn("description", "v2");
+      propPane.TogglePropertyState("Cell wrapping", "On");
+      propPane.NavigateBackToPropertyPane();
+
+      // get the height of the row with the longest text
+      cy.get(".t--widget-tablewidgetv2 .tbody .tr").each(($row) => {
+        cy.wrap($row)
+          .invoke("outerHeight")
+          .then((height) => {
+            if (height !== undefined) {
+              expect(Math.ceil(height)).to.be.greaterThan(DEFAULT_ROW_HEIGHT);
+            }
+          });
+      });
+    });
+
+    it("3. Should update row heights when content changes", () => {
+      // check and store current row height in variable
+      let currentRowHeight = 0;
+      cy.get(".t--widget-tablewidgetv2 .tbody .tr").each(($row) => {
+        cy.wrap($row)
+          .invoke("outerHeight")
+          .then((height) => {
+            if (height !== undefined) {
+              currentRowHeight = Math.ceil(height);
+            }
+          });
+      });
+
+      // updated table data with extermely long text
+      const updatedTestData = [
+        {
+          id: 4,
+          name: "Short text",
+          description: "This is a short description",
+        },
+        {
+          id: 5,
+          name: "Extremely long text",
+          description:
+            "This is an extremely long description that will definitely wrap to multiple lines when cell wrapping is enabled. It contains enough text to ensure that the row height will need to expand significantly to accommodate all the content properly. We're adding even more text here to make sure the row expands further than before. The height measurement should reflect this change in content length appropriately. Additionally, this text continues with more detailed information about how the wrapping behavior works in practice. When dealing with variable height rows, it's important to validate that the table can handle content of any length gracefully. This extra text helps us verify that the row height calculations are working correctly even with very long content that spans multiple lines. The table should automatically adjust the row height to fit all of this content while maintaining proper scrolling and layout behavior. We want to ensure there are no visual glitches or truncation issues when displaying such lengthy content.",
+        },
+      ];
+
+      // update the table data
+      propPane.EnterJSContext("Table data", JSON.stringify(updatedTestData));
+
+      // Find the tallest row in the table
+      let maxHeight = 0;
+      cy.get(".t--widget-tablewidgetv2 .tbody .tr")
+        .each(($row, index) => {
+          cy.wrap($row)
+            .invoke("outerHeight")
+            .then((height) => {
+              if (height !== undefined && height > maxHeight) {
+                maxHeight = height;
+              }
+            });
+        })
+        .then(() => {
+          expect(maxHeight).to.be.greaterThan(currentRowHeight);
+        });
+    });
+
+    it("4. Should revert to fixed height when cell wrapping is disabled", () => {
+      // turn off cell wrapping
+      table.EditColumn("description", "v2");
+      propPane.TogglePropertyState("Cell wrapping", "Off");
+      propPane.NavigateBackToPropertyPane();
+
+      // get the height of the row with the longest text
+      cy.get(".t--widget-tablewidgetv2 .tbody .tr").each(($row) => {
+        cy.wrap($row)
+          .invoke("outerHeight")
+          .then((height) => {
+            if (height !== undefined) {
+              expect(Math.ceil(height)).to.equal(DEFAULT_ROW_HEIGHT);
+            }
+          });
+      });
+    });
+
+    it("5. Should handle HTML content in cells with proper height adjustment", () => {
+      // Create test data with HTML content
+      const htmlTestData = [
+        {
+          id: 6,
+          name: "HTML content",
+          description:
+            "<div>This is a <strong>formatted</strong> description with <br/><br/>multiple line breaks<br/>and formatting</div>",
+        },
+      ];
+
+      // Update the table data
+      propPane.EnterJSContext("Table data", JSON.stringify(htmlTestData));
+
+      // update the column type to html
+      table.EditColumn("description", "v2");
+      propPane.SelectPropertiesDropDown("Column type", "HTML");
+      propPane.NavigateBackToPropertyPane();
+
+      // get the height of the row with the longest text
+      cy.get(".t--widget-tablewidgetv2 .tbody .tr").each(($row) => {
+        cy.wrap($row)
+          .invoke("outerHeight")
+          .then((height) => {
+            if (height !== undefined) {
+              expect(Math.ceil(height)).to.be.greaterThan(DEFAULT_ROW_HEIGHT);
+            }
+          });
+      });
+    });
+  },
+);
diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx
index db17453607dd..95281c1191de 100644
--- a/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx
+++ b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx
@@ -1,24 +1,32 @@
 import type { Key } from "react";
-import React from "react";
+import React, { useEffect, useRef, useState } from "react";
 import type { Row as ReactTableRowType } from "react-table";
-import type { ListChildComponentProps } from "react-window";
+import type { ListChildComponentProps, VariableSizeList } from "react-window";
 import { renderBodyCheckBoxCell } from "../cellComponents/SelectionCheckboxCell";
 import { MULTISELECT_CHECKBOX_WIDTH, StickyType } from "../Constants";
 import { useAppsmithTable } from "../TableContext";
+import { useRowHeightMeasurement } from "../VirtualTable/useRowHeightMeasurement";
+import useColumnVariableHeight from "../VirtualTable/useColumnVariableHeight";
 
 interface RowType {
   className?: string;
   index: number;
   row: ReactTableRowType<Record<string, unknown>>;
   style?: ListChildComponentProps["style"];
+  listRef?: React.RefObject<VariableSizeList>;
+  rowHeights?: React.RefObject<{ [key: number]: number }>;
+  rowNeedsMeasurement?: React.RefObject<{ [key: number]: boolean }>;
 }
 
 export function Row(props: RowType) {
+  const rowRef = useRef<HTMLDivElement>(null);
+  const [forceUpdate, setForceUpdate] = useState(0);
   const {
     accentColor,
     borderRadius,
     columns,
     isAddRowInProgress,
+    isInfiniteScrollEnabled,
     multiRowSelection,
     prepareRow,
     primaryColumnId,
@@ -27,6 +35,27 @@ export function Row(props: RowType) {
     selectTableRow,
   } = useAppsmithTable();
 
+  const wrappingColumns = useColumnVariableHeight(columns, props.row);
+
+  useRowHeightMeasurement({
+    index: props.index,
+    row: props.row,
+    rowRef,
+    rowHeights: props.rowHeights,
+    rowNeedsMeasurement: props.rowNeedsMeasurement,
+    listRef: props.listRef,
+    forceUpdate,
+    isInfiniteScrollEnabled,
+  });
+
+  useEffect(() => {
+    if (props.rowNeedsMeasurement && props.rowNeedsMeasurement.current) {
+      props.rowNeedsMeasurement.current[props.index] = true;
+    }
+
+    setForceUpdate((prev) => prev + 1);
+  }, [wrappingColumns]);
+
   prepareRow?.(props.row);
   const rowProps = {
     ...props.row.getRowProps(),
@@ -34,6 +63,7 @@ export function Row(props: RowType) {
       display: "flex",
       ...(props.style || {}),
     },
+    ref: rowRef,
   };
   const isRowSelected = multiRowSelection
     ? selectedRowIndices.includes(props.row.index)
diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx
index 2c8777ed008c..911e5d21d217 100644
--- a/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx
+++ b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx
@@ -1,33 +1,10 @@
-import type { ListOnItemsRenderedProps, ReactElementType } from "react-window";
-import { FixedSizeList, areEqual } from "react-window";
 import React, { type Ref } from "react";
-import type { ListChildComponentProps } from "react-window";
 import type { Row as ReactTableRowType } from "react-table";
-import { WIDGET_PADDING } from "constants/WidgetConstants";
-import { Row } from "./Row";
-import type { TableSizes } from "../Constants";
+import type { ListOnItemsRenderedProps, ReactElementType } from "react-window";
+import type { VariableSizeList } from "react-window";
 import type SimpleBar from "simplebar-react";
-import { EmptyRows } from "../cellComponents/EmptyCell";
-
-const rowRenderer = React.memo((rowProps: ListChildComponentProps) => {
-  const { data, index, style } = rowProps;
-
-  if (index < data.length) {
-    const row = data[index];
-
-    return (
-      <Row
-        className="t--virtual-row"
-        index={index}
-        key={index}
-        row={row}
-        style={style}
-      />
-    );
-  } else {
-    return <EmptyRows rows={1} style={style} />;
-  }
-}, areEqual);
+import type { TableSizes } from "../Constants";
+import BaseVirtualList from "../VirtualTable/BaseVirtualList";
 
 interface BaseVirtualListProps {
   height: number;
@@ -36,48 +13,16 @@ interface BaseVirtualListProps {
   innerElementType?: ReactElementType;
   outerRef: Ref<SimpleBar>;
   onItemsRendered?: (props: ListOnItemsRenderedProps) => void;
-  infiniteLoaderListRef?: React.Ref<FixedSizeList>;
+  infiniteLoaderListRef?: React.Ref<VariableSizeList>;
   itemCount: number;
   pageSize: number;
 }
 
-const BaseVirtualList = React.memo(function BaseVirtualList({
-  height,
-  infiniteLoaderListRef,
-  innerElementType,
-  itemCount,
-  onItemsRendered,
-  outerRef,
-  rows,
-  tableSizes,
-}: BaseVirtualListProps) {
-  return (
-    <FixedSizeList
-      className="virtual-list simplebar-content"
-      height={
-        height -
-        tableSizes.TABLE_HEADER_HEIGHT -
-        2 * tableSizes.VERTICAL_PADDING
-      }
-      innerElementType={innerElementType}
-      itemCount={itemCount}
-      itemData={rows}
-      itemSize={tableSizes.ROW_HEIGHT}
-      onItemsRendered={onItemsRendered}
-      outerRef={outerRef}
-      ref={infiniteLoaderListRef}
-      width={`calc(100% + ${2 * WIDGET_PADDING}px)`}
-    >
-      {rowRenderer}
-    </FixedSizeList>
-  );
-});
-
 /**
  * The difference between next two components is in the number of arguments they expect.
  */
-export const FixedInfiniteVirtualList = React.memo(
-  function FixedInfiniteVirtualList(props: BaseVirtualListProps) {
+export const VariableInfiniteVirtualList = React.memo(
+  function VariableInfiniteVirtualList(props: BaseVirtualListProps) {
     return <BaseVirtualList {...props} />;
   },
 );
diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx
new file mode 100644
index 000000000000..f355df2bed58
--- /dev/null
+++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx
@@ -0,0 +1,153 @@
+import { WIDGET_PADDING } from "constants/WidgetConstants";
+import React, { useCallback, useMemo, useRef } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type {
+  ListChildComponentProps,
+  ListOnItemsRenderedProps,
+  ReactElementType,
+} from "react-window";
+import { VariableSizeList, areEqual } from "react-window";
+import type SimpleBar from "simplebar-react";
+import type { TableSizes } from "../Constants";
+import { Row } from "../TableBodyCoreComponents/Row";
+import { EmptyRows } from "../cellComponents/EmptyCell";
+
+type ExtendedListChildComponentProps = ListChildComponentProps & {
+  listRef: React.RefObject<VariableSizeList>;
+  rowHeights: React.RefObject<{ [key: number]: number }>;
+  rowNeedsMeasurement: React.RefObject<{ [key: number]: boolean }>;
+};
+
+// Create a memoized row component using areEqual from react-window
+const MemoizedRow = React.memo(
+  (rowProps: ExtendedListChildComponentProps) => {
+    const { data, index, listRef, rowHeights, rowNeedsMeasurement, style } =
+      rowProps;
+
+    if (index < data.length) {
+      const row = data[index];
+
+      return (
+        <Row
+          className="t--virtual-row"
+          index={index}
+          key={index}
+          listRef={listRef}
+          row={row}
+          rowHeights={rowHeights}
+          rowNeedsMeasurement={rowNeedsMeasurement}
+          style={style}
+        />
+      );
+    } else {
+      return <EmptyRows rows={1} style={style} />;
+    }
+  },
+  (prevProps, nextProps) => {
+    // Check if any of the ref props have changed
+    if (
+      prevProps.listRef !== nextProps.listRef ||
+      prevProps.rowHeights !== nextProps.rowHeights ||
+      prevProps.rowNeedsMeasurement !== nextProps.rowNeedsMeasurement
+    ) {
+      return false;
+    }
+
+    // For all other props, use react-window's areEqual
+    return areEqual(prevProps, nextProps);
+  },
+);
+
+export interface BaseVirtualListProps {
+  height: number;
+  tableSizes: TableSizes;
+  rows: ReactTableRowType<Record<string, unknown>>[];
+  innerElementType?: ReactElementType;
+  outerRef?: React.Ref<SimpleBar>;
+  onItemsRendered?: (props: ListOnItemsRenderedProps) => void;
+  infiniteLoaderListRef?: React.Ref<VariableSizeList>;
+  itemCount: number;
+  pageSize: number;
+}
+
+const BaseVirtualList = React.memo(function BaseVirtualList({
+  height,
+  infiniteLoaderListRef,
+  innerElementType,
+  itemCount,
+  onItemsRendered,
+  outerRef,
+  rows,
+  tableSizes,
+}: BaseVirtualListProps) {
+  const listRef = useRef<VariableSizeList>(null);
+  const rowHeights = useRef<{ [key: number]: number }>({});
+  const rowNeedsMeasurement = useRef<{ [key: number]: boolean }>({});
+  const combinedRef = (list: VariableSizeList | null) => {
+    // Handle infiniteLoaderListRef
+    if (infiniteLoaderListRef) {
+      if (typeof infiniteLoaderListRef === "function") {
+        infiniteLoaderListRef(list);
+      } else {
+        (
+          infiniteLoaderListRef as React.MutableRefObject<VariableSizeList | null>
+        ).current = list;
+      }
+    }
+
+    // Handle listRef - only if it's a mutable ref
+    if (listRef && "current" in listRef) {
+      (listRef as React.MutableRefObject<VariableSizeList | null>).current =
+        list;
+    }
+  };
+
+  const getItemSize = useCallback(
+    (index: number) => {
+      try {
+        const rowHeight = rowHeights?.current?.[index] || tableSizes.ROW_HEIGHT;
+
+        return Math.max(rowHeight, tableSizes.ROW_HEIGHT);
+      } catch (error) {
+        return tableSizes.ROW_HEIGHT;
+      }
+    },
+    [rowHeights?.current, tableSizes.ROW_HEIGHT],
+  );
+
+  // Memoize the row renderer function
+  const rowRenderer = useMemo(() => {
+    return (props: ListChildComponentProps) => (
+      <MemoizedRow
+        {...props}
+        listRef={listRef}
+        rowHeights={rowHeights}
+        rowNeedsMeasurement={rowNeedsMeasurement}
+      />
+    );
+  }, [listRef, rowHeights, rowNeedsMeasurement]);
+
+  return (
+    <VariableSizeList
+      className="virtual-list simplebar-content"
+      estimatedItemSize={tableSizes.ROW_HEIGHT}
+      height={
+        height -
+        tableSizes.TABLE_HEADER_HEIGHT -
+        2 * tableSizes.VERTICAL_PADDING
+      }
+      innerElementType={innerElementType}
+      itemCount={itemCount}
+      itemData={rows}
+      itemSize={getItemSize}
+      onItemsRendered={onItemsRendered}
+      outerRef={outerRef}
+      ref={combinedRef}
+      width={`calc(100% + ${2 * WIDGET_PADDING}px)`}
+    >
+      {rowRenderer}
+    </VariableSizeList>
+  );
+});
+
+export default BaseVirtualList;
diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx
index daedfdb589a8..648ca853f812 100644
--- a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx
+++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx
@@ -2,7 +2,7 @@ import React, { type Ref } from "react";
 import { type ReactElementType } from "react-window";
 import InfiniteLoader from "react-window-infinite-loader";
 import type SimpleBar from "simplebar-react";
-import { FixedInfiniteVirtualList } from "../../TableBodyCoreComponents/VirtualList";
+import { VariableInfiniteVirtualList } from "../../TableBodyCoreComponents/VirtualList";
 import { useAppsmithTable } from "../../TableContext";
 import { LoadingIndicator } from "../../LoadingIndicator";
 import { useInfiniteVirtualization } from "./useInfiniteVirtualization";
@@ -40,7 +40,7 @@ const InfiniteScrollBodyComponent = React.forwardRef(
           minimumBatchSize={pageSize}
         >
           {({ onItemsRendered, ref: infiniteLoaderRef }) => (
-            <FixedInfiniteVirtualList
+            <VariableInfiniteVirtualList
               height={height}
               infiniteLoaderListRef={infiniteLoaderRef}
               innerElementType={props.innerElementType}
diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/useColumnVariableHeight.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/useColumnVariableHeight.tsx
new file mode 100644
index 000000000000..7eaa61c7fffc
--- /dev/null
+++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/useColumnVariableHeight.tsx
@@ -0,0 +1,68 @@
+import { useRef } from "react";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import type { ReactTableColumnProps } from "../Constants";
+import type { Row as ReactTableRowType } from "react-table";
+
+function useColumnVariableHeight(
+  columns: ReactTableColumnProps[],
+  row: ReactTableRowType<Record<string, unknown>>,
+) {
+  const variableHeightColumnsRef = useRef<number[]>([]);
+  const previousRowDataRef = useRef<Record<string, unknown> | null>(null);
+
+  const columnsNeedingVariableHeight = columns
+    .map((col, index) => {
+      if (col.columnProperties?.allowCellWrapping) {
+        return index;
+      }
+
+      if (col.columnProperties?.columnType === ColumnTypes.HTML) {
+        return index;
+      }
+
+      return -1;
+    })
+    .filter((index) => index !== -1);
+
+  const columnsConfigChanged =
+    columnsNeedingVariableHeight.length !==
+      variableHeightColumnsRef.current.length ||
+    columnsNeedingVariableHeight.some(
+      (colIndex, i) => colIndex !== variableHeightColumnsRef.current[i],
+    );
+
+  const currentRowData = row?.original || {};
+  const rowDataChanged = hasRowDataChanged(
+    currentRowData,
+    previousRowDataRef.current,
+  );
+
+  if (columnsConfigChanged || rowDataChanged) {
+    variableHeightColumnsRef.current = columnsNeedingVariableHeight;
+    previousRowDataRef.current = { ...currentRowData };
+  }
+
+  return variableHeightColumnsRef.current;
+}
+
+function hasRowDataChanged(
+  currentData: Record<string, unknown>,
+  previousData: Record<string, unknown> | null,
+): boolean {
+  if (!previousData) return true;
+
+  const currentKeys = Object.keys(currentData);
+  const previousKeys = Object.keys(previousData);
+
+  if (currentKeys.length !== previousKeys.length) return true;
+
+  for (const key of currentKeys) {
+    if (currentData[key] !== previousData[key]) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+export default useColumnVariableHeight;
diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
new file mode 100644
index 000000000000..c5ee5e4c54a6
--- /dev/null
+++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
@@ -0,0 +1,159 @@
+import { useEffect, useMemo, type RefObject } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type { VariableSizeList } from "react-window";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import { TABLE_SIZES } from "../Constants";
+
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
+interface UseRowHeightMeasurementProps {
+  index: number; // Index of the current row
+  row: ReactTableRowType<Record<string, unknown>>; // Row data from react-table
+  rowRef?: React.RefObject<HTMLDivElement>; // Reference to the row DOM element
+  rowHeights?: RefObject<{ [key: number]: number }>; // Object storing calculated heights for each row
+  rowNeedsMeasurement?: RefObject<{ [key: number]: boolean }>; // Tracks which rows need height measurement
+  listRef?: RefObject<VariableSizeList> | null; // Reference to the virtualized list component
+  forceUpdate: number; // Trigger to force recalculation of row heights
+  isInfiniteScrollEnabled?: boolean;
+}
+
+/**
+ * Hook for dynamically measuring and managing row heights in a virtualized table
+ *
+ * This hook is crucial for tables with variable row heights when using features like:
+ * - Cell wrapping
+ * - HTML content in cells
+ * - Infinite scrolling
+ *
+ * The hook measures the actual rendered height of specific cells that might affect row height
+ * (cells with wrapping enabled or HTML content) and updates the virtualized list accordingly.
+ *
+ * The measurement process:
+ * 1. Identifies cells that might affect row height (HTML cells or cells with wrapping)
+ * 2. Measures their actual rendered height including margins and padding
+ * 3. Updates the height in rowHeights and marks the row as measured
+ * 4. Notifies the virtualized list to update its internal cache
+ */
+export function useRowHeightMeasurement({
+  forceUpdate,
+  index,
+  isInfiniteScrollEnabled,
+  listRef,
+  row,
+  rowHeights,
+  rowNeedsMeasurement,
+  rowRef,
+}: UseRowHeightMeasurementProps) {
+  const hasPreRequisitesForInfiniteScroll = useMemo(() => {
+    return (
+      isInfiniteScrollEnabled && rowNeedsMeasurement && rowHeights && rowRef
+    );
+  }, [isInfiniteScrollEnabled, rowNeedsMeasurement, rowHeights, rowRef]);
+
+  const isAlreadyCalculated =
+    rowNeedsMeasurement &&
+    rowNeedsMeasurement.current &&
+    rowNeedsMeasurement.current[index] === false;
+
+  const hasRowDataAndUIElement = rowRef?.current && row;
+
+  useEffect(() => {
+    if (!hasPreRequisitesForInfiniteScroll || !hasRowDataAndUIElement) return;
+
+    // Skip measurement if this row was already processed
+    if (isAlreadyCalculated) {
+      return;
+    }
+
+    const element = rowRef?.current;
+
+    if (!hasRowDataAndUIElement) return;
+
+    // Track cells that might affect row height
+    const cellIndexesWithAllowCellWrapping: number[] = [];
+    const cellIndexesWithHTMLCell: number[] = [];
+
+    if (row?.cells && Array.isArray(row.cells)) {
+      row.cells.forEach((cell, index: number) => {
+        const typedCell = cell as unknown as CellWithColumnProps;
+
+        if (typedCell?.column?.columnProperties?.allowCellWrapping) {
+          cellIndexesWithAllowCellWrapping.push(index);
+        }
+
+        if (
+          typedCell?.column?.columnProperties?.columnType === ColumnTypes.HTML
+        ) {
+          cellIndexesWithHTMLCell.push(index);
+        }
+      });
+    }
+
+    // Get all child elements
+    const children = element.children;
+    let normalCellHeight = 0;
+    let htmlCellHeight = 0;
+
+    cellIndexesWithAllowCellWrapping.forEach((index: number) => {
+      const child = children[index] as HTMLElement;
+      const dynamicContent = child.querySelector(
+        ".t--table-cell-tooltip-target",
+      );
+
+      if (dynamicContent) {
+        const styles = window.getComputedStyle(dynamicContent);
+
+        // Calculate total height including margins and padding
+        normalCellHeight +=
+          (dynamicContent as HTMLElement).offsetHeight +
+          parseFloat(styles.marginTop) +
+          parseFloat(styles.marginBottom) +
+          parseFloat(styles.paddingTop) +
+          parseFloat(styles.paddingBottom);
+      }
+    });
+
+    cellIndexesWithHTMLCell.forEach((index: number) => {
+      const child = children[index] as HTMLElement;
+      const dynamicContent = child.querySelector(
+        '[data-testid="t--table-widget-v2-html-cell"]>span',
+      );
+
+      if (dynamicContent) {
+        const styles = window.getComputedStyle(dynamicContent);
+
+        htmlCellHeight +=
+          (dynamicContent as HTMLElement).offsetHeight +
+          parseFloat(styles.marginTop) +
+          parseFloat(styles.marginBottom) +
+          parseFloat(styles.paddingTop) * 2 +
+          parseFloat(styles.paddingBottom) * 2;
+      }
+    });
+
+    const totalHeight =
+      Math.max(normalCellHeight, htmlCellHeight) +
+      TABLE_SIZES.DEFAULT.VERTICAL_PADDING * 2;
+
+    rowHeights?.current && (rowHeights.current[index] = totalHeight);
+    rowNeedsMeasurement?.current &&
+      (rowNeedsMeasurement.current[index] = false);
+    // Notify the virtualized list to update its cache for this row
+    listRef?.current && listRef.current?.resetAfterIndex(index);
+  }, [
+    index,
+    row,
+    forceUpdate,
+    rowHeights,
+    rowNeedsMeasurement,
+    listRef,
+    rowRef,
+  ]);
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx (1)</summary><blockquote>

`48-125`: **Refinement on error handling & optional chaining**  
The measuring logic is well-structured but silently catches all errors, which can obscure unexpected issues. Consider logging or handling them more specifically.  
Meanwhile, applying an optional chain at line 124 aligns with safer property access:

```diff
- rowNeedsMeasurement.current && (rowNeedsMeasurement.current[index] = false);
+ rowNeedsMeasurement.current?.[index] = false;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 124-124: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a20693362bb0d2627511e2dc3ee1b293fdf9c437 and 034bba280a5e8dedc168a58571b583ee9bec79ad.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx` (3 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx` (4 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx` (5 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx</summary>

[error] 124-124: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: client-unit-tests / client-unit-tests
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-prettier / prettier-check

</details>

<details>
<summary>🔇 Additional comments (20)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx (7)</summary>

`2-2`: **Looks good.**  
This import is appropriate for the new hooks usage and poses no issues.

---

`8-12`: **Constants import**  
Neat import of constants for table sizing and styling.

---

`21-27`: **Clear interface definition**  
This interface addition keeps the cell props explicit, aiding readability.

---

`31-31`: **Prop destructuring**  
Extracting props here promotes clarity.

---

`37-37`: **Retrieving listRef**  
Grabbing the list reference from context is a clean approach for virtualization.

---

`41-42`: **Context-based row measurements**  
Acquiring rowHeights and rowNeedsMeasurement from context effectively enables dynamic row resizing.

---

`160-160`: **Ref assignment**  
Binding rowRef to the row DOM element is crucial for accurate height measurement.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx (6)</summary>

`2-9`: **Necessary imports**  
Bringing in the relevant types and hooks for variable-size virtualization.

---

`11-13`: **BodyContext usage**  
Importing BodyContext aligns the virtual list with the dynamic row measurement paradigm.

---

`42-42`: **Ref type update**  
Switching to React.Ref<VariableSizeList> is consistent with the variable sizing approach.

---

`57-57`: **Destructuring from context**  
Extracting listRef and rowHeights sets up the foundation for dynamic sizing logic.

---

`59-89`: **Combining refs & computing item size**  
Merging the infiniteLoaderListRef and listRef is convenient, and the getItemSize callback nicely accommodates variable row heights.

---

`92-110`: **VariableSizeList integration**  
Leveraging dynamic row sizing with an estimated item size is a solid choice for better rendering performance.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx (7)</summary>

`1-1`: **Imports**  
No issues; this suits the new hooks and effect usage.

---

`7-7`: **Importing VariableSizeList**  
Needed to enable variable row height logic.

---

`36-38`: **Enhancement to BodyContextType**  
Adding rowHeights, rowNeedsMeasurement, and listRef defines the backbone for measuring rows.

---

`53-55`: **Default context initialization**  
Providing default ref objects ensures consistent behavior throughout.

---

`142-145`: **Creating ref state**  
The useRef hooks for listRef, rowHeights, and rowNeedsMeasurement help centralize row dimension tracking.

---

`147-149`: **Row measurement reset**  
Reinitializing rowNeedsMeasurement on row changes is a clean way to handle newly rendered rows.

---

`182-185`: **Context provider additions**  
Populating rowHeights, rowNeedsMeasurement, and listRef in BodyContext completes the dynamic resizing infrastructure.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T08:03:52Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/BaseVirtualList.tsx (2)</summary><blockquote>

`78-90`: **Consider updating useCallback dependency array**

The `useCallback` hook dependency array includes `rowHeights.current` which is a mutable ref value and won't trigger proper dependency updates.

```diff
-  [rowHeights.current, tableSizes.ROW_HEIGHT],
+  [tableSizes.ROW_HEIGHT], // rowHeights is a ref, so we don't need it in the deps array
```

---

`80-87`: **Add error logging for better debugging**

Your error handling silently falls back to default row height when an error occurs. Consider adding error logging to make debugging easier.

```diff
  try {
    // Add a minimum height threshold to prevent rows from being too small
    const rowHeight = rowHeights.current?.[index] || tableSizes.ROW_HEIGHT;

    return Math.max(rowHeight, tableSizes.ROW_HEIGHT);
  } catch (error) {
+   console.error("Error calculating row height:", error);
    return tableSizes.ROW_HEIGHT;
  }
```

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/useColumnVariableHeight.tsx (3)</summary><blockquote>

`55-75`: **Improve comparison for nested objects**

The current implementation of `hasRowDataChanged` performs shallow comparison, which won't detect changes in nested objects.

```diff
function hasRowDataChanged(
  currentData: Record<string, unknown>,
  previousData: Record<string, unknown> | null,
): boolean {
  if (!previousData) return true;

  // Check if the keys are different
  const currentKeys = Object.keys(currentData);
  const previousKeys = Object.keys(previousData);

  if (currentKeys.length !== previousKeys.length) return true;

  // Check if any values have changed for the variable height columns
  for (const key of currentKeys) {
-   if (currentData[key] !== previousData[key]) {
+   if (JSON.stringify(currentData[key]) !== JSON.stringify(previousData[key])) {
      return true;
    }
  }

  return false;
}
```

---

`6-52`: **Add null/undefined check for columns**

The hook doesn't handle the case where columns might be null or undefined, which could cause runtime errors.

```diff
function useColumnVariableHeight(
  columns: ReactTableColumnProps[],
  row: ReactTableRowType<Record<string, unknown>>,
) {
  const variableHeightColumnsRef = useRef<number[]>([]);
  const previousRowDataRef = useRef<Record<string, unknown> | null>(null);

+  // Return empty array if columns are not defined
+  if (!columns || !Array.isArray(columns)) {
+    return [];
+  }

  // Identify columns that need variable height handling (wrapping or HTML)
  const columnsNeedingVariableHeight = columns
    .map((col, index) => {
      // Check for columns with wrapping enabled
      if (col.columnProperties?.allowCellWrapping) {
        return index;
      }

      // Check for with HTML type
      if (col.columnProperties?.columnType === ColumnTypes.HTML) {
        return index;
      }

      return -1;
    })
    .filter((index) => index !== -1);
```

---

`6-52`: **Optimize row data change detection for large datasets**

The hook checks all keys in row data rather than just those for variable height columns, which could be inefficient for large datasets.


Consider optimizing to only check relevant columns:

```diff
  // Check if row data has changed
  const currentRowData = row?.original || {};
+ 
+ // Only check columns that need variable height
+ const relevantKeys = Object.keys(currentRowData).filter((key, idx) => 
+   columnsNeedingVariableHeight.includes(idx));
+ 
+ const relevantCurrentData = relevantKeys.reduce((obj, key) => {
+   obj[key] = currentRowData[key];
+   return obj;
+ }, {} as Record<string, unknown>);
+ 
+ const relevantPreviousData = previousRowDataRef.current ? relevantKeys.reduce((obj, key) => {
+   obj[key] = previousRowDataRef.current?.[key];
+   return obj;
+ }, {} as Record<string, unknown>) : null;

  const rowDataChanged = hasRowDataChanged(
-   currentRowData,
-   previousRowDataRef.current,
+   relevantCurrentData,
+   relevantPreviousData,
  );
```

</blockquote></details>
<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts (2)</summary><blockquote>

`72-116`: **Avoid nested async operations with better structure**

The test uses nested async operations which can lead to flaky tests. Consider restructuring to avoid unnecessary nesting.

```diff
- let currentRowHeight = 0;
- cy.get(".t--widget-tablewidgetv2 .tbody .tr").each(($row) => {
-   cy.wrap($row)
-     .invoke("outerHeight")
-     .then((height) => {
-       currentRowHeight = Math.ceil(height!);
-     });
- });

+ // Get the height of the first row before update
+ cy.get(".t--widget-tablewidgetv2 .tbody .tr")
+   .first()
+   .invoke("outerHeight")
+   .then((height) => {
+     const currentRowHeight = Math.ceil(height!);
      
+     // Update the table data
+     propPane.EnterJSContext("Table data", JSON.stringify(updatedTestData));
      
+     // Verify height change
+     cy.get(".t--widget-tablewidgetv2 .tbody .tr")
+       .invoke("outerHeight")
+       .should("be.greaterThan", currentRowHeight);
+   });
```

---

`103-115`: **Replace manual max height calculation with Cypress commands**

The manual calculation of max height can be replaced with Cypress commands for better readability and reliability.

```diff
- let maxHeight = 0;
- cy.get(".t--widget-tablewidgetv2 .tbody .tr")
-   .each(($row, index) => {
-     cy.wrap($row)
-       .invoke("outerHeight")
-       .then((height) => {
-         if (height! > maxHeight) {
-           maxHeight = height!;
-         }
-       });
-   })
-   .then(() => {
-     expect(maxHeight).to.be.greaterThan(currentRowHeight);
-   });

+ // Use Cypress to directly find the tallest row
+ cy.get(".t--widget-tablewidgetv2 .tbody .tr")
+   .then(($rows) => {
+     let maxHeight = 0;
+     $rows.each((_, row) => {
+       const height = Cypress.$(row).outerHeight();
+       maxHeight = Math.max(maxHeight, height || 0);
+     });
+     expect(maxHeight).to.be.greaterThan(currentRowHeight);
+   });
```

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx (4)</summary><blockquote>

`53-56`: **Consider memoizing dependency to prevent unnecessary renders.**

The current implementation will trigger re-renders whenever `wrappingColumns` changes. Consider memoizing this value or implementing shouldComponentUpdate logic to prevent unnecessary re-renders.

---

`57-87`: **Extract cell type identification logic into a separate function.**

The logic for identifying cells with wrapping and HTML content could be extracted into a separate function to improve readability and maintainability.

```diff
- if (row?.cells && Array.isArray(row.cells)) {
-   row.cells.forEach((cell, index: number) => {
-     const typedCell = cell as unknown as CellWithColumnProps;
- 
-     if (typedCell?.column?.columnProperties?.allowCellWrapping) {
-       cellIndexesWithAllowCellWrapping.push(index);
-     }
- 
-     if (
-       typedCell?.column?.columnProperties?.columnType === ColumnTypes.HTML
-     ) {
-       cellIndexesWithHTMLCell.push(index);
-     }
-   });
- }
+ if (row?.cells && Array.isArray(row.cells)) {
+   const identifyCellTypes = (cell: any, index: number) => {
+     const typedCell = cell as unknown as CellWithColumnProps;
+     
+     if (typedCell?.column?.columnProperties?.allowCellWrapping) {
+       cellIndexesWithAllowCellWrapping.push(index);
+     }
+     
+     if (typedCell?.column?.columnProperties?.columnType === ColumnTypes.HTML) {
+       cellIndexesWithHTMLCell.push(index);
+     }
+   };
+   
+   row.cells.forEach(identifyCellTypes);
+ }
```

---

`88-132`: **Extract height calculation logic into separate functions.**

The height calculation logic is complex and would benefit from being extracted into separate functions for better readability and maintainability.

```diff
- // Get all child elements
- const children = element.children;
- let normalCellHeight = 0;
- let htmlCellHeight = 0;
- 
- cellIndexesWithAllowCellWrapping.forEach((index: number) => {
-   const child = children[index] as HTMLElement;
-   const dynamicContent = child.querySelector(
-     ".t--table-cell-tooltip-target",
-   );
- 
-   if (dynamicContent) {
-     const styles = window.getComputedStyle(dynamicContent);
- 
-     normalCellHeight +=
-       (dynamicContent as HTMLElement).offsetHeight +
-       parseFloat(styles.marginTop) +
-       parseFloat(styles.marginBottom) +
-       parseFloat(styles.paddingTop) +
-       parseFloat(styles.paddingBottom);
-   }
- });
- 
- cellIndexesWithHTMLCell.forEach((index: number) => {
-   const child = children[index] as HTMLElement;
-   const dynamicContent = child.querySelector(
-     '[data-testid="t--table-widget-v2-html-cell"]>span',
-   );
- 
-   if (dynamicContent) {
-     const styles = window.getComputedStyle(dynamicContent);
- 
-     htmlCellHeight +=
-       (dynamicContent as HTMLElement).offsetHeight +
-       parseFloat(styles.marginTop) +
-       parseFloat(styles.marginBottom) +
-       parseFloat(styles.paddingTop) * 2 +
-       parseFloat(styles.paddingBottom) * 2;
-   }
- });
+ // Get all child elements
+ const children = element.children;
+ 
+ const calculateWrappingCellsHeight = () => {
+   let height = 0;
+   cellIndexesWithAllowCellWrapping.forEach((index: number) => {
+     const child = children[index] as HTMLElement;
+     const dynamicContent = child.querySelector(
+       ".t--table-cell-tooltip-target",
+     );
+     
+     if (dynamicContent) {
+       const styles = window.getComputedStyle(dynamicContent);
+       
+       height +=
+         (dynamicContent as HTMLElement).offsetHeight +
+         parseFloat(styles.marginTop) +
+         parseFloat(styles.marginBottom) +
+         parseFloat(styles.paddingTop) +
+         parseFloat(styles.paddingBottom);
+     }
+   });
+   return height;
+ };
+ 
+ const calculateHtmlCellsHeight = () => {
+   let height = 0;
+   cellIndexesWithHTMLCell.forEach((index: number) => {
+     const child = children[index] as HTMLElement;
+     const dynamicContent = child.querySelector(
+       '[data-testid="t--table-widget-v2-html-cell"]>span',
+     );
+     
+     if (dynamicContent) {
+       const styles = window.getComputedStyle(dynamicContent);
+       
+       height +=
+         (dynamicContent as HTMLElement).offsetHeight +
+         parseFloat(styles.marginTop) +
+         parseFloat(styles.marginBottom) +
+         parseFloat(styles.paddingTop) * 2 +
+         parseFloat(styles.paddingBottom) * 2;
+     }
+   });
+   return height;
+ };
+ 
+ const normalCellHeight = calculateWrappingCellsHeight();
+ const htmlCellHeight = calculateHtmlCellsHeight();
```

---

`133-136`: **Use optional chaining as suggested by static analysis.**

Implement optional chaining for safer property access on line 135.

```diff
- listRef && listRef.current?.resetAfterIndex(index);
+ listRef?.current?.resetAfterIndex(index);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 135-135: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 034bba280a5e8dedc168a58571b583ee9bec79ad and 90d6218834bce6442a29bab960e721ca0ae462df.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/BaseVirtualList.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/BodyContext.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/index.tsx` (2 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx` (3 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx` (3 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/useColumnVariableHeight.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`app/client/cypress/**/**.*`: Review the following e2e test ...</summary>

> `app/client/cypress/**/**.*`: Review the following e2e test code written using the Cypress test library. Ensure that:
> - Follow best practices for Cypress code and e2e automation. 
> - Avoid using cy.wait in code.
> - Avoid using cy.pause in code.
> - Avoid using agHelper.sleep().
> - Use locator variables for locators and do not use plain strings.
> - Use data-* attributes for selectors.
> - Avoid Xpaths, Attributes and CSS path.
> - Avoid selectors like .btn.submit or button[type=submit].
> - Perform logins via API with LoginFromAPI.
> - Perform logout via API with LogOutviaAPI.
> - Perform signup via API with SignupFromAPI.
> - Avoid using it.only.
> - Avoid using after and aftereach in test cases.
> - Use multiple assertions for expect statements.
> - Avoid using strings for assertions.
> - Do not use duplicate filenames even with different paths.
> - Avoid using agHelper.Sleep, this.Sleep in any file in code.
> 

- `app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts`

</details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx</summary>

[error] 135-135: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: client-lint / client-lint
* GitHub Check: client-unit-tests / client-unit-tests
* GitHub Check: client-build / client-build
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-prettier / prettier-check

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx (1)</summary>

`70-70`: **Added new props for dynamic row height support**

The new props `listRef`, `rowHeights`, and `rowNeedsMeasurement` provide the necessary infrastructure for variable row height functionality in the TableBody component. These props will allow the table to measure and track row heights dynamically based on content.



Also applies to: 77-78

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/BodyContext.tsx (1)</summary>

`1-52`: **Well-structured context implementation for variable height management**

This new context properly encapsulates the state needed for variable height rows. The use of RefObject types for tracking row heights and measurement flags is appropriate, and importing VariableSizeList from react-window shows the intention to support dynamic row sizing.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx (1)</summary>

`87-87`: **Consistent implementation of row height props in StaticTable**

The addition of `listRef`, `rowHeights`, and `rowNeedsMeasurement` props maintains consistency with the VirtualTable implementation, ensuring that both table types support the variable height functionality.



Also applies to: 93-94

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/index.tsx (2)</summary>

`8-8`: **Replaced fixed height list with variable height implementation**

Swapping `FixedInfiniteVirtualList` for `VariableHeightInfiniteVirtualList` is the key change enabling dynamic row heights. This modification aligns perfectly with the PR objective of supporting variable height rows in the Table Widget.



Also applies to: 49-49

---

`49-59`: <details>
<summary>✅ Verification successful</summary>

**Verify prop compatibility with the new component**

The props being passed to `VariableHeightInfiniteVirtualList` appear unchanged from those previously passed to `FixedInfiniteVirtualList`. Ensure that the new component handles these props correctly, especially considering its enhanced capability to manage variable heights.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the implementation of VariableHeightInfiniteVirtualList to verify prop compatibility
echo "Checking VirtualList implementation to verify prop types and handling..."
cat app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx
```

Length of output: 886

---



</details>

**Prop Compatibility Verified**  
The implementation of `VariableHeightInfiniteVirtualList` shows that it forwards all props—including `onItemsRendered` and `infiniteLoaderListRef`—to `BaseVirtualList` without alteration. This confirms compatibility with the props previously passed to `FixedInfiniteVirtualList`.

- The new component is a simple wrapper around `BaseVirtualList`, ensuring that any variable height handling is seamlessly integrated.
- No additional alterations are required based on the verification.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/BaseVirtualList.tsx (1)</summary>

`47-113`: **Implementation looks good**

The BaseVirtualList component is well-structured with proper ref handling and memoization for performance optimization. The approach for calculating variable row heights is solid.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx (2)</summary>

`7-11`: **Component renaming reflects functionality change**

Good job renaming `FixedInfiniteVirtualList` to `VariableHeightInfiniteVirtualList` to better reflect its purpose with the new variable height functionality.

---

`13-21`: **Clean separation of fixed vs variable list components**

The separation of concerns between `FixedVirtualList` and `VariableHeightInfiniteVirtualList` is well implemented, making the code more maintainable.

</details>
<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/InfiniteScrollVariableHeightRows_spec.ts (2)</summary>

`10-44`: **Tests setup looks good**

The test setup using the feature flag intercept and appropriate test data with varying text lengths is well structured.

---

`46-161`: **Comprehensive test cases**

The tests cover all the key scenarios for variable height rows: fixed height baseline, dynamic height with wrapping, height updates on content change, reversion to fixed height, and HTML content handling.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx (5)</summary>

`2-14`: **Clean import organization for new functionality.**

The imports have been properly organized to support the new dynamic height measurement functionality. Good addition of the necessary hooks and constants.

---

`23-30`: **Well-defined interface for column properties.**

The `CellWithColumnProps` interface clearly defines the structure needed for handling cell wrapping and column types, which is essential for the dynamic height calculations.

---

`33-52`: **Good setup of references and state for dynamic measurements.**

The component now properly uses refs and state to track row elements and handle dynamic updates based on wrapping columns.

---

`171-171`: **Good addition of the ref attribute to the row element.**

The ref attribute is properly added to the row element, which is essential for the height measurement logic.

---

`69-71`: <details>
<summary>❓ Verification inconclusive</summary>

**Review the hardcoded index in cellIndexesWithHTMLCell array.**

The array is initialized with `[0]` which assumes the first cell is always an HTML cell. Is this intentional? If not, consider initializing as an empty array.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the first cell is always HTML in the table
ast-grep --pattern 'cellIndexesWithHTMLCell: number[] = [0]'
```

Length of output: 62

---



</details>

**Action Required: Confirm the Intent Behind the Hardcoded Index**

It’s not entirely clear from the code whether hardcoding `[0]` for `cellIndexesWithHTMLCell` is intentional. Please verify if the first cell should always be an HTML cell; if so, consider adding a clarifying comment to document this design decision. Otherwise, initialize the array as empty to avoid accidental assumptions.

- **Location:** `app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx` (Lines 69-71)

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:11:00Z
user: login: rahulbarwal
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:49:16Z
user: login: jacquesikot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:10:54Z
user: login: jacquesikot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:11:24Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/useRowHeightMeasurement.tsx (1)</summary><blockquote>

`117-125`: **Use optional chaining for clarity.**  
Static analysis suggests replacing  
```js
listRef && listRef.current?.resetAfterIndex(index);
```  
with  
```js
listRef?.current?.resetAfterIndex(index);
```  
to simplify the condition.  

Apply this diff to lines 123-124:

```diff
-    listRef && listRef.current?.resetAfterIndex(index);
+    listRef?.current?.resetAfterIndex(index);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5b8a9ccb40ffd29588b81129feca242bc0ec695a and f32f509c97d1475732a9e1d21a7bb141520ee70d.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `app/client/src/widgets/TableWidgetV2/component/TableBody/BodyContext.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx` (3 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx` (3 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/useColumnVariableHeight.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBody/useRowHeightMeasurement.tsx` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* app/client/src/widgets/TableWidgetV2/component/TableBody/BodyContext.tsx
* app/client/src/widgets/TableWidgetV2/component/TableBody/useColumnVariableHeight.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/useRowHeightMeasurement.tsx</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx (8)</summary>

`2-2`: **All good on adding the React imports.**  
No issues noted in importing `useContext`, `useEffect`, `useRef`, and `useState`.

---

`8-10`: **New hook and context imports look correct.**  
These imports seamlessly integrate the body context and variable height hooks.

---

`20-21`: **Clear destructuring and ref usage.**  
Destructuring `index` and `row`, and creating `rowRef` for height measurement is a straightforward approach.

---

`27-28`: **Context usage is appropriate.**  
Extracting `isInfiniteScrollEnabled`, `listRef`, `rowHeights`, and `rowNeedsMeasurement` here is consistent with dynamic row measurement logic.  



Also applies to: 32-33

---

`38-39`: **Double-check potential render loops with `forceUpdate`.**  
Repeatedly incrementing `forceUpdate` whenever `wrappingColumns` changes may cause extra re-renders.  

Please confirm that `wrappingColumns` remains stable across renders to avoid performance issues.

---

`41-43`: **Potential re-render risk in the effect.**  
Ensure that `wrappingColumns` doesn’t receive a new reference on every render, or memoize it if necessary to prevent infinite re-renders.

---

`45-54`: **Properly invoking `useRowHeightMeasurement`.**  
All required parameters are passed in correctly, aligning with the hook’s signature.

---

`89-89`: **Ref assignment aligns with measurement usage.**  
Assigning `ref={rowRef}` correctly supports the row height measurement logic.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/useRowHeightMeasurement.tsx (1)</summary>

`1-115`: **Comprehensive row height measurement logic.**  
The hook correctly checks if measurement is needed, gathers wrapping/HTML cells, and calculates maximum height. This approach ensures dynamic row expansion for various cell types. Watch out for potential performance concerns if the table has a large number of rows, but this is otherwise a solid implementation.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx (6)</summary>

`1-1`: **New imports for React hooks appear appropriate.**  
The addition of `useEffect` and `useRef` is in line with the new measurement logic.

---

`7-7`: **`VariableSizeList` import is valid.**  
No issues observed with importing from `react-window`.

---

`10-10`: **`TableSizes` import usage.**  
Helpful for referencing size constants in table functionalities.

---

`14-14`: **BodyContext type import is consistent.**  
Centralizing the context logic is a neat improvement for better maintainability.

---

`99-107`: **Ref management for dynamic row heights.**  
`listRef`, `rowHeights`, and `rowNeedsMeasurement` references are well-structured for measuring and invalidating row heights on changes.

---

`139-142`: **Passing new fields into the BodyContext.**  
Including `rowHeights`, `rowNeedsMeasurement`, `listRef`, and `isInfiniteScrollEnabled` is consistent with the dynamic row-measurement flow.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:26:38Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx (1)</summary><blockquote>

`60-77`: **Consider simplifying ref handling logic**

The combined ref handling is complex but necessary. However, it could be improved by abstracting the ref assignment logic into a separate utility function to make the code more maintainable.

```diff
- const combinedRef = (list: VariableSizeList | null) => {
-   // Handle infiniteLoaderListRef
-   if (infiniteLoaderListRef) {
-     if (typeof infiniteLoaderListRef === "function") {
-       infiniteLoaderListRef(list);
-     } else {
-       (
-         infiniteLoaderListRef as React.MutableRefObject<VariableSizeList | null>
-       ).current = list;
-     }
-   }
-
-   // Handle listRef - only if it's a mutable ref
-   if (listRef && "current" in listRef) {
-     (listRef as React.MutableRefObject<VariableSizeList | null>).current =
-       list;
-   }
- };
+ const combinedRef = (list: VariableSizeList | null) => {
+   // Utility function to update a ref
+   const updateRef = (ref: React.Ref<VariableSizeList> | null | undefined) => {
+     if (!ref) return;
+     if (typeof ref === "function") {
+       ref(list);
+     } else if ("current" in ref) {
+       (ref as React.MutableRefObject<VariableSizeList | null>).current = list;
+     }
+   };
+   
+   updateRef(infiniteLoaderListRef);
+   updateRef(listRef);
+ };
```

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (2)</summary><blockquote>

`76-116`: **DOM measurement could be more robust**

The DOM measurement logic works but should be more defensive against potential layout changes. Consider adding null checks for each child and dynamicContent access.

```diff
 cellIndexesWithAllowCellWrapping.forEach((index: number) => {
   const child = children[index] as HTMLElement;
+  if (!child) return;
   const dynamicContent = child.querySelector(
     ".t--table-cell-tooltip-target",
   );

-  if (dynamicContent) {
+  if (dynamicContent instanceof HTMLElement) {
     const styles = window.getComputedStyle(dynamicContent);

     normalCellHeight +=
-      (dynamicContent as HTMLElement).offsetHeight +
+      dynamicContent.offsetHeight +
       parseFloat(styles.marginTop) +
       parseFloat(styles.marginBottom) +
       parseFloat(styles.paddingTop) +
       parseFloat(styles.paddingBottom);
   }
 });
```

---

`117-123`: **Use optional chaining for list reset**

Fix the optional chaining issue flagged by static analysis.

```diff
- listRef && listRef.current?.resetAfterIndex(index);
+ listRef?.current?.resetAfterIndex(index);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useColumnVariableHeight.tsx (1)</summary><blockquote>

`48-66`: **hasRowDataChanged could be optimized for large datasets**

The implementation is correct but performs a shallow comparison on each property. For large datasets, consider implementing a more efficient equality check or using a memoization library.

```diff
function hasRowDataChanged(
  currentData: Record<string, unknown>,
  previousData: Record<string, unknown> | null,
): boolean {
  if (!previousData) return true;

  const currentKeys = Object.keys(currentData);
  const previousKeys = Object.keys(previousData);

  if (currentKeys.length !== previousKeys.length) return true;

+  // Fast path: Check if references are equal first
+  if (currentData === previousData) return false;
+
+  // For large datasets, consider using a faster equality check or memoization
  for (const key of currentKeys) {
    if (currentData[key] !== previousData[key]) {
      return true;
    }
  }

  return false;
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f32f509c97d1475732a9e1d21a7bb141520ee70d and 69f64f742bf1855eb1592f6b8ee545eb43782f70.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useColumnVariableHeight.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx (3)</summary>

`16-34`: **Efficient implementation of rowRenderer with memoization**

The memoized rowRenderer implementation is efficient and correctly prevents unnecessary re-renders through the areEqual comparison function.

---

`79-91`: **Robust row height calculation with fallback**

The getItemSize implementation correctly handles fallback to default row height when measurements aren't available, ensuring the UI doesn't break.

---

`93-113`: **Well-structured VariableSizeList configuration**

The VariableSizeList configuration properly accounts for headers and padding in height calculations, which is essential for accurate virtual rendering.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (2)</summary>

`37-56`: **Effective early returns for performance optimization**

The implementation properly uses early returns to avoid unnecessary calculations, which is good for performance.

---

`57-75`: **Comprehensive cell type detection**

The detection of cells needing variable height is well-implemented, checking both for wrapping and HTML content types.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useColumnVariableHeight.tsx (1)</summary>

`6-46`: **Efficient column variable height detection**

The useColumnVariableHeight hook efficiently identifies and tracks columns needing variable height handling. The implementation correctly caches results and only updates when necessary.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T12:33:17Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (1)</summary><blockquote>

`27-133`: **Consider optional chaining for safer assignments**  
At line 123, adopting optional chaining may simplify and clarify the index assignment:  

```diff
- rowNeedsMeasurement.current && (rowNeedsMeasurement.current[index] = false);
+ rowNeedsMeasurement.current?.[index] = false;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 69f64f742bf1855eb1592f6b8ee545eb43782f70 and 922f26a629f7e93099ddaf5e96328f6d09416663.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx` (2 hunks)
* `app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx` (2 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx` (1 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx` (2 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx (1) (1)</summary>

* `VariableInfiniteVirtualList` (24:28)

</details>

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx (1) (1)</summary>

* `Row` (21:131)

</details>

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx (1) (1)</summary>

* `BaseVirtualListProps` (45:55)

</details>

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (1) (1)</summary>

* `useRowHeightMeasurement` (27:133)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: perform-test / client-build / client-build
* GitHub Check: perform-test / server-build / server-unit-tests
* GitHub Check: perform-test / rts-build / build
* GitHub Check: client-prettier / prettier-check
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-unit-tests / client-unit-tests

</details>

<details>
<summary>🔇 Additional comments (14)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx (2)</summary>

`5-5`: **Introduce VariableInfiniteVirtualList for dynamic heights**  
Swapping from `FixedInfiniteVirtualList` to `VariableInfiniteVirtualList` aligns perfectly with the goal of supporting variable row heights.

---

`43-53`: <details>
<summary>❓ Verification inconclusive</summary>

**Confirm correct prop passing in VariableInfiniteVirtualList**  
Everything appears consistent with the dynamic row-height approach. Verify that props like `onItemsRendered` and `infiniteLoaderListRef` are fully tested with infinite scrolling.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify occurrences of VariableInfiniteVirtualList usage across the codebase
rg "VariableInfiniteVirtualList"
```

Length of output: 641

---



</details>

**Action: Verify Infinite Scroll Prop Passing and Testing Coverage**

The usage of `VariableInfiniteVirtualList` in this file appears to align with the dynamic row-height approach. However, please ensure that key props such as `onItemsRendered` and `infiniteLoaderListRef` are thoroughly covered in your infinite scrolling test cases. In particular:

- Confirm that tests effectively simulate infinite scroll scenarios to validate that `onItemsRendered` is invoked at the proper times.
- Verify that `infiniteLoaderListRef` is properly handled within the component, as observed in its usage across the codebase (see import from `TableBodyCoreComponents/VirtualList`).

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/BaseVirtualList.tsx (4)</summary>

`1-19`: **Imports and extended props look good**  
Imports correctly support variable-sized virtualization with react-window. No issues found.

---

`21-43`: **rowRenderer function is well-structured**  
Appropriately handles both valid row data and empty rows. Meshes nicely with the Row component.

---

`45-55`: **BaseVirtualListProps definition is comprehensive**  
Thoroughly covers references, props, and item details needed for variable-size rendering.

---

`57-130`: **BaseVirtualList component is properly implemented**  
Smart use of `useCallback` for dynamic row sizing and a careful default fallback enhance clarity and performance.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (1)</summary>

`1-25`: **Initial interfaces are well-defined**  
They neatly outline the data structures and references needed for measuring row heights.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx (2)</summary>

`3-6`: **Correct type imports for variable-size usage**  
Switching to `VariableSizeList` types is correct for dynamic row heights. No issues here.  



Also applies to: 16-16

---

`24-25`: **VariableInfiniteVirtualList meets dynamic row needs**  
Renaming from fixed to variable infinite list is well-structured. The memoized component usage looks good.

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx (5)</summary>

`2-4`: **Imports properly updated for variable row height functionality.**

The new imports support the implementation of dynamic row height calculations for the Table Widget's infinite scroll functionality.



Also applies to: 8-9

---

`16-18`: **Added well-typed props for row height management.**

These new properties provide the necessary references for the variable height implementation, allowing row heights to be measured, stored, and used by the virtual list component.

---

`22-24`: **Added ref and state management for row measurement.**

The rowRef provides access to the DOM element for measurement, while forceUpdate creates a mechanism to trigger re-renders when needed. The isInfiniteScrollEnabled flag is appropriately extracted from context.



Also applies to: 29-29

---

`38-58`: **Implemented row height measurement with appropriate hooks and effects.**

The implementation correctly:
1. Uses useColumnVariableHeight to identify columns needing variable height
2. Uses useRowHeightMeasurement to calculate actual row heights
3. Implements an effect to mark rows for remeasurement when wrapping columns change

The dependency array in the useEffect correctly includes wrappingColumns, ensuring updates happen when needed.

---

`66-66`: **Connected ref to row DOM element for measurement.**

Adding the ref to rowProps ensures the DOM element can be accessed for height measurement.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T14:18:54Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (6)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (3)</summary><blockquote>

`58-58`: **Consider clarifying why index 0 is pre-populated.**

The array initialization with `[0]` isn't immediately obvious. Consider adding a comment explaining why index 0 is included by default in `cellIndexesWithHTMLCell`.


```diff
-    const cellIndexesWithHTMLCell: number[] = [0];
+    // Include index 0 by default as it often contains HTML content (e.g., row number or selection cell)
+    const cellIndexesWithHTMLCell: number[] = [0];
```

---

`81-97`: **Extract repeated height calculation logic into a helper function.**

Both the wrapping cell and HTML cell height calculations follow a similar pattern. Consider extracting this to reduce duplication.


```diff
+    const calculateElementHeight = (element: HTMLElement, selector: string) => {
+      const dynamicContent = element.querySelector(selector);
+      if (!dynamicContent) return 0;
+      
+      const styles = window.getComputedStyle(dynamicContent);
+      return (dynamicContent as HTMLElement).offsetHeight +
+        parseFloat(styles.marginTop) +
+        parseFloat(styles.marginBottom) +
+        parseFloat(styles.paddingTop) +
+        parseFloat(styles.paddingBottom);
+    };
+
     cellIndexesWithAllowCellWrapping.forEach((index: number) => {
       const child = children[index] as HTMLElement;
-      const dynamicContent = child.querySelector(
-        ".t--table-cell-tooltip-target",
-      );
-
-      if (dynamicContent) {
-        const styles = window.getComputedStyle(dynamicContent);
-
-        normalCellHeight +=
-          (dynamicContent as HTMLElement).offsetHeight +
-          parseFloat(styles.marginTop) +
-          parseFloat(styles.marginBottom) +
-          parseFloat(styles.paddingTop) +
-          parseFloat(styles.paddingBottom);
-      }
+      normalCellHeight += calculateElementHeight(child, ".t--table-cell-tooltip-target");
     });

     cellIndexesWithHTMLCell.forEach((index: number) => {
       const child = children[index] as HTMLElement;
-      const dynamicContent = child.querySelector(
-        '[data-testid="t--table-widget-v2-html-cell"]>span',
-      );
-
-      if (dynamicContent) {
-        const styles = window.getComputedStyle(dynamicContent);
-
-        htmlCellHeight +=
-          (dynamicContent as HTMLElement).offsetHeight +
-          parseFloat(styles.marginTop) +
-          parseFloat(styles.marginBottom) +
-          parseFloat(styles.paddingTop) * 2 +
-          parseFloat(styles.paddingBottom) * 2;
-      }
+      // Note: HTML cells need padding multiplied by 2
+      const height = calculateElementHeight(child, '[data-testid="t--table-widget-v2-html-cell"]>span');
+      if (height > 0) {
+        const styles = window.getComputedStyle(child.querySelector('[data-testid="t--table-widget-v2-html-cell"]>span'));
+        htmlCellHeight += height + parseFloat(styles.paddingTop) + parseFloat(styles.paddingBottom);
+      }
     });
```


Also applies to: 99-115

---

`123-123`: **Use optional chaining for listRef access.**

As suggested by the static analysis tool, using optional chaining can make the code more concise and safer.


```diff
-    listRef && listRef.current?.resetAfterIndex(index);
+    listRef?.current?.resetAfterIndex(index);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx (3)</summary><blockquote>

`51-57`: **Use optional chaining for safer property access.**

The current check could be simplified with optional chaining for safer property access.


```diff
-    if (props.rowNeedsMeasurement && props.rowNeedsMeasurement.current) {
+    if (props.rowNeedsMeasurement?.current) {
       props.rowNeedsMeasurement.current[props.index] = true;
     }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 52-52: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`51-57`: **Consider memoizing wrappingColumns to prevent unnecessary re-renders.**

The `useEffect` is triggered on every change to `wrappingColumns`, which could lead to frequent state updates and potentially unnecessary re-renders.


```diff
-  const wrappingColumns = useColumnVariableHeight(columns, props.row);
+  const wrappingColumns = React.useMemo(
+    () => useColumnVariableHeight(columns, props.row),
+    [columns, props.row]
+  );
```

This might not be necessary if `useColumnVariableHeight` is already memoized internally, but it's a good practice to consider.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 52-52: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`22-23`: **Consider initializing forceUpdate with a callback function.**

Using a callback in the useState initialization is generally preferred for consistency.


```diff
-  const [forceUpdate, setForceUpdate] = useState(0);
+  const [forceUpdate, setForceUpdate] = useState(() => 0);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 922f26a629f7e93099ddaf5e96328f6d09416663 and c724e0a5affe92f3eeb0789bec41c4b05e1a5eab.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx` (2 hunks)
* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx</summary>

[error] 52-52: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: perform-test / rts-build / build
* GitHub Check: perform-test / client-build / client-build
* GitHub Check: perform-test / server-build / server-unit-tests
* GitHub Check: client-unit-tests / client-unit-tests
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-prettier / prettier-check

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (1)</summary>

`27-133`: **Well-implemented hook with good separation of concerns.**

The implementation effectively handles dynamic row height measurement for both wrapped text and HTML content. The early return conditions are well-structured, and the effect dependencies are correctly specified.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 123-123: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>
<details>
<summary>app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx (1)</summary>

`38-49`: **Good integration of the height measurement hook.**

The integration of `useRowHeightMeasurement` with the appropriate props looks good. This cleanly separates the measurement logic from the rendering component.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T20:38:38Z
user: login: rahulbarwal
body: Does this PR also account for the possibility of multiple columns allowing cell wrapping and HTML content? In such cases, we should ensure that the maximum height is taken into consideration. I haven't explored this in detail, but please ensure that we handle this scenario appropriately.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T05:28:15Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (5)</summary><blockquote>

`54-65`: **Use consistent memoization to improve performance**

Good decomposition of logical conditions into readable variables. However, `isAlreadyCalculated` could also benefit from memoization like `hasPreRequisitesForInfiniteScroll`.

```diff
- const isAlreadyCalculated =
-   rowNeedsMeasurement &&
-   rowNeedsMeasurement.current &&
-   rowNeedsMeasurement.current[index] === false;
+ const isAlreadyCalculated = useMemo(() => {
+   return (
+     rowNeedsMeasurement &&
+     rowNeedsMeasurement.current &&
+     rowNeedsMeasurement.current[index] === false
+   );
+ }, [rowNeedsMeasurement, index]);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 61-62: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`77-77`: **Remove redundant check**

This check is redundant since `hasRowDataAndUIElement` is already checked at line 68.

```diff
- if (!hasRowDataAndUIElement) return;
```

---

`61-64`: **Apply optional chaining to simplify null checks**

The static analysis tool correctly identified that optional chaining can simplify these null checks.

```diff
- const isAlreadyCalculated =
-   rowNeedsMeasurement &&
-   rowNeedsMeasurement.current &&
-   rowNeedsMeasurement.current[index] === false;
+ const isAlreadyCalculated =
+   rowNeedsMeasurement?.current?.[index] === false;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 61-62: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`149-149`: **Apply optional chaining to simplify redundant checks**

Use optional chaining to simplify this line based on the static analysis suggestion.

```diff
- listRef?.current && listRef.current?.resetAfterIndex(index);
+ listRef?.current?.resetAfterIndex(index);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 149-149: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`104-121`: **Extract cell height measurement into a helper function**

The logic for measuring cell heights is duplicated between normal cells and HTML cells. Consider extracting this logic into a reusable helper function.

```diff
+ const measureCellHeight = (
+   element: HTMLElement,
+   selector: string,
+   extraPaddingMultiplier = 1
+ ): number => {
+   const dynamicContent = element.querySelector(selector);
+   if (!dynamicContent) return 0;
+ 
+   const styles = window.getComputedStyle(dynamicContent);
+   return (
+     (dynamicContent as HTMLElement).offsetHeight +
+     parseFloat(styles.marginTop) +
+     parseFloat(styles.marginBottom) +
+     parseFloat(styles.paddingTop) * extraPaddingMultiplier +
+     parseFloat(styles.paddingBottom) * extraPaddingMultiplier
+   );
+ };

  cellIndexesWithAllowCellWrapping.forEach((index: number) => {
    const child = children[index] as HTMLElement;
-   const dynamicContent = child.querySelector(
-     ".t--table-cell-tooltip-target",
-   );
-
-   if (dynamicContent) {
-     const styles = window.getComputedStyle(dynamicContent);
-
-     // Calculate total height including margins and padding
-     normalCellHeight +=
-       (dynamicContent as HTMLElement).offsetHeight +
-       parseFloat(styles.marginTop) +
-       parseFloat(styles.marginBottom) +
-       parseFloat(styles.paddingTop) +
-       parseFloat(styles.paddingBottom);
-   }
+   normalCellHeight += measureCellHeight(
+     child,
+     ".t--table-cell-tooltip-target"
+   );
  });

  cellIndexesWithHTMLCell.forEach((index: number) => {
    const child = children[index] as HTMLElement;
-   const dynamicContent = child.querySelector(
-     '[data-testid="t--table-widget-v2-html-cell"]>span',
-   );
-
-   if (dynamicContent) {
-     const styles = window.getComputedStyle(dynamicContent);
-
-     htmlCellHeight +=
-       (dynamicContent as HTMLElement).offsetHeight +
-       parseFloat(styles.marginTop) +
-       parseFloat(styles.marginBottom) +
-       parseFloat(styles.paddingTop) * 2 +
-       parseFloat(styles.paddingBottom) * 2;
-   }
+   htmlCellHeight += measureCellHeight(
+     child,
+     '[data-testid="t--table-widget-v2-html-cell"]>span',
+     2
+   );
  });
```


Also applies to: 123-139

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 895490222103909086c84dab3d8b9b94df81a4af and d8eddca5b0fbc2407b250e237b06292269b02530.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx</summary>

[error] 61-62: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 149-149: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (20)</summary>

* GitHub Check: perform-test / ci-test / ci-test (19)
* GitHub Check: perform-test / ci-test / ci-test (18)
* GitHub Check: perform-test / ci-test / ci-test (17)
* GitHub Check: perform-test / ci-test / ci-test (16)
* GitHub Check: perform-test / ci-test / ci-test (15)
* GitHub Check: perform-test / ci-test / ci-test (14)
* GitHub Check: perform-test / ci-test / ci-test (13)
* GitHub Check: perform-test / ci-test / ci-test (12)
* GitHub Check: perform-test / ci-test / ci-test (11)
* GitHub Check: perform-test / ci-test / ci-test (10)
* GitHub Check: perform-test / ci-test / ci-test (9)
* GitHub Check: perform-test / ci-test / ci-test (8)
* GitHub Check: perform-test / ci-test / ci-test (7)
* GitHub Check: perform-test / ci-test / ci-test (6)
* GitHub Check: perform-test / ci-test / ci-test (5)
* GitHub Check: perform-test / ci-test / ci-test (4)
* GitHub Check: perform-test / ci-test / ci-test (3)
* GitHub Check: perform-test / ci-test / ci-test (2)
* GitHub Check: perform-test / ci-test / ci-test (1)
* GitHub Check: perform-test / ci-test / ci-test (0)

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (3)</summary>

`27-43`: **Good documentation implementation**

Excellent JSDoc comment that clearly explains the hook's purpose, when it should be used, and the measurement process. This addresses the previous reviewer's request for detailed comments.

---

`44-53`: **Good implementation of the hook interface**

The hook's parameter destructuring is well-organized and matches the interface definition. This makes the code more maintainable and self-documenting.

---

`141-149`: <details>
<summary>✅ Verification successful</summary>

**Consider adding a minimum height fallback**

The current implementation calculates the total height based on measured elements, but doesn't include a fallback minimum height if measurements fail or return very small values.

```diff
  const totalHeight =
-   Math.max(normalCellHeight, htmlCellHeight) +
+   Math.max(normalCellHeight, htmlCellHeight, TABLE_SIZES.DEFAULT.ROW_HEIGHT) +
    TABLE_SIZES.DEFAULT.VERTICAL_PADDING * 2;
```

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if TABLE_SIZES.DEFAULT.ROW_HEIGHT exists
grep -r "ROW_HEIGHT" app/client/src/widgets/TableWidgetV2/component/Constants.ts
```

Length of output: 162

---



</details>

**Minimum Height Fallback Verified and Recommended**

We've confirmed that the constant `TABLE_SIZES.DEFAULT.ROW_HEIGHT` exists in the constants file (evidenced by the grep search). To ensure a fallback for cases where measured values are too small or fail, please update the Math.max computation as follows:

- **File:** `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx` (Lines 141-149)
- **Change:** Replace  
  ```ts
  Math.max(normalCellHeight, htmlCellHeight) +
  ```
  with  
  ```ts
  Math.max(normalCellHeight, htmlCellHeight, TABLE_SIZES.DEFAULT.ROW_HEIGHT) +
  ```
  
This change ensures that the calculated row height will always be at least the defined default, improving stability when measurements are inadequate.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 149-149: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:06:31Z
user: login: rahulbarwal
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:11:59Z
user: login: rahulbarwal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:13:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (4)</summary><blockquote>

`60-63`: **Simplify with optional chaining.**

Use optional chaining to simplify the condition and avoid potential null/undefined errors.

```diff
-  const isAlreadyCalculated =
-    rowNeedsMeasurement &&
-    rowNeedsMeasurement.current &&
-    rowNeedsMeasurement.current[index] === false;
+  const isAlreadyCalculated = rowNeedsMeasurement?.current?.[index] === false;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 61-62: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`83-97`: **Consider optimizing the cell identification process.**

The current implementation potentially processes the same cell twice if it both has HTML content and allows wrapping. Consider tracking these attributes together in a single pass.

```diff
-    const cellIndexesWithAllowCellWrapping: number[] = [];
-    const cellIndexesWithHTMLCell: number[] = [];
+    interface CellMeasurementInfo {
+      index: number;
+      hasWrapping: boolean;
+      isHtmlCell: boolean;
+    }
+    const cellsToMeasure: CellMeasurementInfo[] = [];

     if (row?.cells && Array.isArray(row.cells)) {
       row.cells.forEach((cell, index: number) => {
         const typedCell = cell as unknown as CellWithColumnProps;
+        const hasWrapping = !!typedCell?.column?.columnProperties?.allowCellWrapping;
+        const isHtmlCell = typedCell?.column?.columnProperties?.columnType === ColumnTypes.HTML;
 
-        if (typedCell?.column?.columnProperties?.allowCellWrapping) {
-          cellIndexesWithAllowCellWrapping.push(index);
-        }
-
-        if (
-          typedCell?.column?.columnProperties?.columnType === ColumnTypes.HTML
-        ) {
-          cellIndexesWithHTMLCell.push(index);
+        if (hasWrapping || isHtmlCell) {
+          cellsToMeasure.push({ index, hasWrapping, isHtmlCell });
         }
       });
     }
```

---

`104-139`: **Combine height calculation logic for better maintainability.**

With the refactored cell identification process, you could also simplify the height calculation for better maintainability.

```diff
-    cellIndexesWithAllowCellWrapping.forEach((index: number) => {
-      const child = children[index] as HTMLElement;
-      const dynamicContent = child.querySelector(
-        ".t--table-cell-tooltip-target",
-      );
-
-      if (dynamicContent) {
-        const styles = window.getComputedStyle(dynamicContent);
-
-        // Calculate total height including margins and padding
-        normalCellHeight +=
-          (dynamicContent as HTMLElement).offsetHeight +
-          parseFloat(styles.marginTop) +
-          parseFloat(styles.marginBottom) +
-          parseFloat(styles.paddingTop) +
-          parseFloat(styles.paddingBottom);
-      }
-    });
-
-    cellIndexesWithHTMLCell.forEach((index: number) => {
-      const child = children[index] as HTMLElement;
-      const dynamicContent = child.querySelector(
-        '[data-testid="t--table-widget-v2-html-cell"]>span',
-      );
-
-      if (dynamicContent) {
-        const styles = window.getComputedStyle(dynamicContent);
-
-        htmlCellHeight +=
-          (dynamicContent as HTMLElement).offsetHeight +
-          parseFloat(styles.marginTop) +
-          parseFloat(styles.marginBottom) +
-          parseFloat(styles.paddingTop) * 2 +
-          parseFloat(styles.paddingBottom) * 2;
-      }
-    });
+    let maxCellHeight = 0;
+
+    cellsToMeasure.forEach(({ index, hasWrapping, isHtmlCell }) => {
+      const child = children[index] as HTMLElement;
+      let cellHeight = 0;
+      
+      if (hasWrapping) {
+        const dynamicContent = child.querySelector(".t--table-cell-tooltip-target");
+        if (dynamicContent) {
+          const styles = window.getComputedStyle(dynamicContent);
+          cellHeight = Math.max(cellHeight,
+            (dynamicContent as HTMLElement).offsetHeight +
+            parseFloat(styles.marginTop) +
+            parseFloat(styles.marginBottom) +
+            parseFloat(styles.paddingTop) +
+            parseFloat(styles.paddingBottom));
+        }
+      }
+      
+      if (isHtmlCell) {
+        const dynamicContent = child.querySelector('[data-testid="t--table-widget-v2-html-cell"]>span');
+        if (dynamicContent) {
+          const styles = window.getComputedStyle(dynamicContent);
+          cellHeight = Math.max(cellHeight,
+            (dynamicContent as HTMLElement).offsetHeight +
+            parseFloat(styles.marginTop) +
+            parseFloat(styles.marginBottom) +
+            parseFloat(styles.paddingTop) * 2 +
+            parseFloat(styles.paddingBottom) * 2);
+        }
+      }
+      
+      maxCellHeight = Math.max(maxCellHeight, cellHeight);
+    });
+
+    const totalHeight = maxCellHeight + TABLE_SIZES.DEFAULT.VERTICAL_PADDING * 2;
```

---

`149-149`: **Simplify optional chaining.**

Use consistent optional chaining to improve readability.

```diff
-    listRef?.current && listRef.current?.resetAfterIndex(index);
+    listRef?.current?.resetAfterIndex(index);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 149-149: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro (Legacy)**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d8eddca5b0fbc2407b250e237b06292269b02530 and 9f50f22adcb8a8ab9c2aa566c7a0f21e49d1beee.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx</summary>

[error] 61-62: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 149-149: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: perform-test / client-build / client-build
* GitHub Check: perform-test / rts-build / build
* GitHub Check: perform-test / server-build / server-unit-tests
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-lint / client-lint
* GitHub Check: client-unit-tests / client-unit-tests
* GitHub Check: client-prettier / prettier-check

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx (4)</summary>

`16-25`: **Well-structured props interface with clear type annotations.**

The interface is well-defined with proper TypeScript types and clear documentation comments explaining each property.

---

`27-43`: **Excellent JSDoc documentation.**

The documentation clearly explains the purpose, use cases, and measurement process of the hook. This will help other developers understand and maintain the code.

---

`54-58`: **Good use of useMemo for derived values.**

Using useMemo for the prerequisites calculation prevents unnecessary recalculations on re-renders.

---

`67-158`: **Thorough implementation with clear logic.**

The overall implementation of the useEffect hook is robust and handles edge cases well. It correctly identifies cells that might affect row height, measures them appropriately, and updates the virtualized list to reflect these measurements.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 149-149: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:49:45Z
user: login: rahulbarwal
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T04:29:27Z

Review comments:
diff_hunk: @@ -84,11 +84,14 @@ const StaticTable = (props: StaticTableProps, ref: React.Ref<SimpleBar>) => {
         height={props.height}
         isAddRowInProgress={props.isAddRowInProgress}
         isLoading={props.isLoading}
+        listRef={null}
path: app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx
user: login: rahulbarwal
body: These props should be optional at the source.
created_at: 2025-03-12T13:43:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,51 @@
+import React, { type RefObject } from "react";
+import type {
+  Row as ReactTableRowType,
+  TableBodyPropGetter,
+  TableBodyProps,
+} from "react-table";
+import type { VariableSizeList } from "react-window";
+import type { ReactTableColumnProps } from "../Constants";
+import type { HeaderComponentProps } from "../Table";
+
+export type BodyContextType = {
path: app/client/src/widgets/TableWidgetV2/component/TableBody/BodyContext.tsx
user: login: rahulbarwal
body: Changes to this will become moot after my [refactor PR](https://github.com/appsmithorg/appsmith/pull/39367#pullrequestreview-2678498692) goes in.
created_at: 2025-03-12T13:44:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,19 +20,120 @@ interface RowType {
   style?: ListChildComponentProps["style"];
 }
 
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
 export function Row(props: RowType) {
+  const { index, row } = props;
+  const rowRef = useRef<HTMLDivElement>(null);
   const {
     accentColor,
     borderRadius,
     columns,
     isAddRowInProgress,
+    listRef,
     multiRowSelection,
     prepareRow,
     primaryColumnId,
+    rowHeights,
+    rowNeedsMeasurement,
     selectedRowIndex,
     selectedRowIndices,
     selectTableRow,
   } = useContext(BodyContext);
+  const [forceUpdate, setForceUpdate] = useState(0);
+  const wrappingColumns = useColumnVariableHeight(columns, row);
+
+  useEffect(() => {
+    setForceUpdate((prev) => prev + 1);
+  }, [wrappingColumns]);
+
+  useEffect(() => {
path: app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx
user: login: rahulbarwal
body: I think this useeffect is adding complexity to this component. Lets create a custom hook for this as well. 
Pros:
1. Isolated change
2. better unit testing. 

When you do it, add a check at top, to not do anything if infinteScroll is disabled.
created_at: 2025-03-12T13:46:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,82 +1,11 @@
-import type { ListOnItemsRenderedProps, ReactElementType } from "react-window";
-import { FixedSizeList, areEqual } from "react-window";
 import React from "react";
-import type { ListChildComponentProps } from "react-window";
-import type { Row as ReactTableRowType } from "react-table";
-import { WIDGET_PADDING } from "constants/WidgetConstants";
-import { EmptyRow, Row } from "./Row";
-import type { TableSizes } from "../Constants";
-import type SimpleBar from "simplebar-react";
-
-const rowRenderer = React.memo((rowProps: ListChildComponentProps) => {
-  const { data, index, style } = rowProps;
-
-  if (index < data.length) {
-    const row = data[index];
-
-    return (
-      <Row
-        className="t--virtual-row"
-        index={index}
-        key={index}
-        row={row}
-        style={style}
-      />
-    );
-  } else {
-    return <EmptyRow style={style} />;
-  }
-}, areEqual);
-
-interface BaseVirtualListProps {
-  height: number;
-  tableSizes: TableSizes;
-  rows: ReactTableRowType<Record<string, unknown>>[];
-  innerElementType?: ReactElementType;
-  outerRef?: React.Ref<SimpleBar>;
-  onItemsRendered?: (props: ListOnItemsRenderedProps) => void;
-  infiniteLoaderListRef?: React.Ref<FixedSizeList>;
-  itemCount: number;
-  pageSize: number;
-}
-
-const BaseVirtualList = React.memo(function BaseVirtualList({
-  height,
-  infiniteLoaderListRef,
-  innerElementType,
-  itemCount,
-  onItemsRendered,
-  outerRef,
-  rows,
-  tableSizes,
-}: BaseVirtualListProps) {
-  return (
-    <FixedSizeList
-      className="virtual-list simplebar-content"
-      height={
-        height -
-        tableSizes.TABLE_HEADER_HEIGHT -
-        2 * tableSizes.VERTICAL_PADDING
-      }
-      innerElementType={innerElementType}
-      itemCount={itemCount}
-      itemData={rows}
-      itemSize={tableSizes.ROW_HEIGHT}
-      onItemsRendered={onItemsRendered}
-      outerRef={outerRef}
-      ref={infiniteLoaderListRef}
-      width={`calc(100% + ${2 * WIDGET_PADDING}px)`}
-    >
-      {rowRenderer}
-    </FixedSizeList>
-  );
-});
+import BaseVirtualList, { type BaseVirtualListProps } from "./BaseVirtualList";
path: app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx
user: login: rahulbarwal
body: I would give it a more explicit name that explains the nature of the component it renders. (I mean it should convey something about dynamic sizing.)
created_at: 2025-03-12T13:48:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+import { useRef } from "react";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import type { ReactTableColumnProps } from "../Constants";
+import type { Row as ReactTableRowType } from "react-table";
+
+function useColumnVariableHeight(
+  columns: ReactTableColumnProps[],
+  row: ReactTableRowType<Record<string, unknown>>,
+) {
+  const variableHeightColumnsRef = useRef<number[]>([]);
+  const previousRowDataRef = useRef<Record<string, unknown> | null>(null);
+
+  // Identify columns that need variable height handling (wrapping or HTML)
+  const columnsNeedingVariableHeight = columns
+    .map((col, index) => {
+      // Check for columns with wrapping enabled
+      if (col.columnProperties?.allowCellWrapping) {
+        return index;
+      }
+
+      // Check for with HTML type
+      if (col.columnProperties?.columnType === ColumnTypes.HTML) {
+        return index;
+      }
+
+      return -1;
+    })
+    .filter((index) => index !== -1);
+
+  // Check if variable height configuration has changed
+  const columnsConfigChanged =
+    columnsNeedingVariableHeight.length !==
+      variableHeightColumnsRef.current.length ||
+    columnsNeedingVariableHeight.some(
+      (colIndex, i) => colIndex !== variableHeightColumnsRef.current[i],
+    );
+
+  // Check if row data has changed
+  const currentRowData = row?.original || {};
+  const rowDataChanged = hasRowDataChanged(
+    currentRowData,
+    previousRowDataRef.current,
+  );
+
+  // Update refs if configuration or data changed
+  if (columnsConfigChanged || rowDataChanged) {
+    variableHeightColumnsRef.current = columnsNeedingVariableHeight;
+    previousRowDataRef.current = { ...currentRowData };
+  }
+
+  return variableHeightColumnsRef.current;
+}
+
+// Helper function to check if row data has changed
path: app/client/src/widgets/TableWidgetV2/component/TableBody/useColumnVariableHeight.tsx
user: login: rahulbarwal
body: unnecessary comment.
created_at: 2025-03-12T13:48:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+import { useRef } from "react";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import type { ReactTableColumnProps } from "../Constants";
+import type { Row as ReactTableRowType } from "react-table";
+
+function useColumnVariableHeight(
+  columns: ReactTableColumnProps[],
+  row: ReactTableRowType<Record<string, unknown>>,
+) {
+  const variableHeightColumnsRef = useRef<number[]>([]);
+  const previousRowDataRef = useRef<Record<string, unknown> | null>(null);
+
+  // Identify columns that need variable height handling (wrapping or HTML)
+  const columnsNeedingVariableHeight = columns
+    .map((col, index) => {
+      // Check for columns with wrapping enabled
+      if (col.columnProperties?.allowCellWrapping) {
+        return index;
+      }
+
+      // Check for with HTML type
+      if (col.columnProperties?.columnType === ColumnTypes.HTML) {
+        return index;
+      }
+
+      return -1;
+    })
+    .filter((index) => index !== -1);
+
+  // Check if variable height configuration has changed
path: app/client/src/widgets/TableWidgetV2/component/TableBody/useColumnVariableHeight.tsx
user: login: rahulbarwal
body: remove all such ai generated comments that don't help us.
created_at: 2025-03-12T13:48:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,19 +20,120 @@ interface RowType {
   style?: ListChildComponentProps["style"];
 }
 
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
 export function Row(props: RowType) {
+  const { index, row } = props;
+  const rowRef = useRef<HTMLDivElement>(null);
   const {
     accentColor,
     borderRadius,
     columns,
     isAddRowInProgress,
+    listRef,
     multiRowSelection,
     prepareRow,
     primaryColumnId,
+    rowHeights,
+    rowNeedsMeasurement,
     selectedRowIndex,
     selectedRowIndices,
     selectTableRow,
   } = useContext(BodyContext);
+  const [forceUpdate, setForceUpdate] = useState(0);
+  const wrappingColumns = useColumnVariableHeight(columns, row);
+
+  useEffect(() => {
+    setForceUpdate((prev) => prev + 1);
+  }, [wrappingColumns]);
+
+  useEffect(() => {
path: app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx
user: login: jacquesikot
body: Makes sense, I have made the chage
created_at: 2025-03-13T10:10:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -84,11 +84,14 @@ const StaticTable = (props: StaticTableProps, ref: React.Ref<SimpleBar>) => {
         height={props.height}
         isAddRowInProgress={props.isAddRowInProgress}
         isLoading={props.isLoading}
+        listRef={null}
path: app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx
user: login: jacquesikot
body: makes sense
created_at: 2025-03-13T10:11:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,133 @@
+import { useEffect, type RefObject } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type { VariableSizeList } from "react-window";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import { TABLE_SIZES } from "../Constants";
+
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
+interface UseRowHeightMeasurementProps {
+  index: number;
+  row: ReactTableRowType<Record<string, unknown>>;
+  rowRef?: React.RefObject<HTMLDivElement>;
+  rowHeights?: RefObject<{ [key: number]: number }>;
+  rowNeedsMeasurement?: RefObject<{ [key: number]: boolean }>;
+  listRef?: RefObject<VariableSizeList> | null;
+  forceUpdate: number;
+  isInfiniteScrollEnabled?: boolean;
+}
+
+export function useRowHeightMeasurement({
+  forceUpdate,
+  index,
+  isInfiniteScrollEnabled,
+  listRef,
+  row,
+  rowHeights,
+  rowNeedsMeasurement,
+  rowRef,
+}: UseRowHeightMeasurementProps) {
+  useEffect(() => {
+    if (
+      !isInfiniteScrollEnabled ||
+      !rowNeedsMeasurement ||
+      !rowHeights ||
+      !rowRef
+    )
+      return;
+
+    if (
+      rowNeedsMeasurement.current &&
+      rowNeedsMeasurement.current[index] === false
+    ) {
+      return;
+    }
+
+    const element = rowRef.current;
+
+    if (!element || !row) return;
path: app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
user: login: rahulbarwal
body: Why do we have multiple ifs? you can improve the readability by using variable and single if: 
```suggestion
const hasPreRequisitesForInfiniteScroll  = isInfiniteScrollEnabled && rowHeights && rowRef;

const isAlreadyCalculated = rowNeedsMeasurement && 
      rowNeedsMeasurement.current &&
      rowNeedsMeasurement.current[index] === false;
      
const hasRowDataAndUIElement = rowRef.current && row;

    if (isAlreadyCalculated && !hasPreRequisitesForInfiniteScroll ||  !hasRowDataAndUIElement) return;
```
created_at: 2025-03-19T05:22:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,133 @@
+import { useEffect, type RefObject } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type { VariableSizeList } from "react-window";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import { TABLE_SIZES } from "../Constants";
+
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
+interface UseRowHeightMeasurementProps {
+  index: number;
+  row: ReactTableRowType<Record<string, unknown>>;
+  rowRef?: React.RefObject<HTMLDivElement>;
+  rowHeights?: RefObject<{ [key: number]: number }>;
+  rowNeedsMeasurement?: RefObject<{ [key: number]: boolean }>;
+  listRef?: RefObject<VariableSizeList> | null;
+  forceUpdate: number;
+  isInfiniteScrollEnabled?: boolean;
+}
+
+export function useRowHeightMeasurement({
path: app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
user: login: rahulbarwal
body: Please add a detailed comment explaining the functionality of this hook, including the significance of each variable, particularly those that allow for exceptions or play a crucial role in its operation.
created_at: 2025-03-19T05:24:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,159 @@
+import { useEffect, useMemo, type RefObject } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type { VariableSizeList } from "react-window";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import { TABLE_SIZES } from "../Constants";
+
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
+interface UseRowHeightMeasurementProps {
+  index: number; // Index of the current row
+  row: ReactTableRowType<Record<string, unknown>>; // Row data from react-table
+  rowRef?: React.RefObject<HTMLDivElement>; // Reference to the row DOM element
+  rowHeights?: RefObject<{ [key: number]: number }>; // Object storing calculated heights for each row
+  rowNeedsMeasurement?: RefObject<{ [key: number]: boolean }>; // Tracks which rows need height measurement
+  listRef?: RefObject<VariableSizeList> | null; // Reference to the virtualized list component
+  forceUpdate: number; // Trigger to force recalculation of row heights
+  isInfiniteScrollEnabled?: boolean;
+}
+
+/**
+ * Hook for dynamically measuring and managing row heights in a virtualized table
+ *
+ * This hook is crucial for tables with variable row heights when using features like:
+ * - Cell wrapping
+ * - HTML content in cells
+ * - Infinite scrolling
+ *
+ * The hook measures the actual rendered height of specific cells that might affect row height
+ * (cells with wrapping enabled or HTML content) and updates the virtualized list accordingly.
+ *
+ * The measurement process:
+ * 1. Identifies cells that might affect row height (HTML cells or cells with wrapping)
+ * 2. Measures their actual rendered height including margins and padding
+ * 3. Updates the height in rowHeights and marks the row as measured
+ * 4. Notifies the virtualized list to update its internal cache
+ */
+export function useRowHeightMeasurement({
+  forceUpdate,
+  index,
+  isInfiniteScrollEnabled,
+  listRef,
+  row,
+  rowHeights,
+  rowNeedsMeasurement,
+  rowRef,
+}: UseRowHeightMeasurementProps) {
+  const hasPreRequisitesForInfiniteScroll = useMemo(() => {
+    return (
+      isInfiniteScrollEnabled && rowNeedsMeasurement && rowHeights && rowRef
+    );
+  }, [isInfiniteScrollEnabled, rowNeedsMeasurement, rowHeights, rowRef]);
+
+  const isAlreadyCalculated =
+    rowNeedsMeasurement &&
+    rowNeedsMeasurement.current &&
+    rowNeedsMeasurement.current[index] === false;
+
+  const hasRowDataAndUIElement = rowRef?.current && row;
+
+  useEffect(() => {
+    if (!hasPreRequisitesForInfiniteScroll || !hasRowDataAndUIElement) return;
+
+    // Skip measurement if this row was already processed
+    if (isAlreadyCalculated) {
+      return;
+    }
+
+    const element = rowRef?.current;
+
+    if (!hasRowDataAndUIElement) return;
+
+    // Track cells that might affect row height
+    const cellIndexesWithAllowCellWrapping: number[] = [];
+    const cellIndexesWithHTMLCell: number[] = [0];
path: app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Clarify the purpose of default cell inclusion**

The first cell (index 0) is always included in `cellIndexesWithHTMLCell`. Add a comment explaining why, or remove it if this is unintentional.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there's a reason this element is always included
rg -A 3 "cellIndexesWithHTMLCell" app/client/src/widgets/TableWidgetV2/component/
```

Length of output: 1589

---



</details>

**Action Required: Add a clarifying comment on the default cell inclusion**

The code always includes index 0 in the array, which is subsequently processed to measure row heights. However, it's unclear why the first cell is forcefully added. Please add a brief inline comment explaining the rationale (for example, noting that the cell at index 0 serves as the baseline for measuring dynamic content) unless this behavior is unintentional and should be removed.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T12:06:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,133 @@
+import { useEffect, type RefObject } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type { VariableSizeList } from "react-window";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import { TABLE_SIZES } from "../Constants";
+
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
+interface UseRowHeightMeasurementProps {
+  index: number;
+  row: ReactTableRowType<Record<string, unknown>>;
+  rowRef?: React.RefObject<HTMLDivElement>;
+  rowHeights?: RefObject<{ [key: number]: number }>;
+  rowNeedsMeasurement?: RefObject<{ [key: number]: boolean }>;
+  listRef?: RefObject<VariableSizeList> | null;
+  forceUpdate: number;
+  isInfiniteScrollEnabled?: boolean;
+}
+
+export function useRowHeightMeasurement({
path: app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
user: login: rahulbarwal
body: Done.
created_at: 2025-03-19T12:11:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,133 @@
+import { useEffect, type RefObject } from "react";
+import type { Row as ReactTableRowType } from "react-table";
+import type { VariableSizeList } from "react-window";
+import { ColumnTypes } from "widgets/TableWidgetV2/constants";
+import { TABLE_SIZES } from "../Constants";
+
+interface CellWithColumnProps {
+  column: {
+    columnProperties?: {
+      allowCellWrapping?: boolean;
+      columnType: ColumnTypes;
+    };
+  };
+}
+
+interface UseRowHeightMeasurementProps {
+  index: number;
+  row: ReactTableRowType<Record<string, unknown>>;
+  rowRef?: React.RefObject<HTMLDivElement>;
+  rowHeights?: RefObject<{ [key: number]: number }>;
+  rowNeedsMeasurement?: RefObject<{ [key: number]: boolean }>;
+  listRef?: RefObject<VariableSizeList> | null;
+  forceUpdate: number;
+  isInfiniteScrollEnabled?: boolean;
+}
+
+export function useRowHeightMeasurement({
+  forceUpdate,
+  index,
+  isInfiniteScrollEnabled,
+  listRef,
+  row,
+  rowHeights,
+  rowNeedsMeasurement,
+  rowRef,
+}: UseRowHeightMeasurementProps) {
+  useEffect(() => {
+    if (
+      !isInfiniteScrollEnabled ||
+      !rowNeedsMeasurement ||
+      !rowHeights ||
+      !rowRef
+    )
+      return;
+
+    if (
+      rowNeedsMeasurement.current &&
+      rowNeedsMeasurement.current[index] === false
+    ) {
+      return;
+    }
+
+    const element = rowRef.current;
+
+    if (!element || !row) return;
path: app/client/src/widgets/TableWidgetV2/component/VirtualTable/useRowHeightMeasurement.tsx
user: login: rahulbarwal
body: Done.
created_at: 2025-03-19T12:13:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-10T08:00:57Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces a new Cypress test suite to validate the dynamic row height behavior of the Table Widget. In addition, it adds a new `BaseVirtualList` component for virtualized rendering of table rows, along with two custom hooks—`useColumnVariableHeight` and `useRowHeightMeasurement`—to manage dynamic column and row height adjustments during infinite scrolling.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `app/client/cypress/.../TableV2/InfiniteScrollVariableHeightRows_spec.ts` | Adds a new test suite with multiple cases for verifying fixed, dynamic, and HTML-based row height changes under infinite scrolling. |
| `app/client/src/widgets/.../BaseVirtualList.tsx` | Introduces the `BaseVirtualList` component leveraging `react-window` for efficiently rendering variable height rows with error handling and ref management. |
| `app/client/src/widgets/.../useColumnVariableHeight.tsx` <br> `app/client/src/widgets/.../useRowHeightMeasurement.tsx` | Adds two new hooks: one to detect variable height columns based on wrapping/HTML and another to compute and update row heights by measuring DOM elements. |
| `app/client/src/widgets/.../Row.tsx` | Updates the `Row` component to manage dynamic row heights and state, integrating new hooks and properties. |
| `app/client/src/widgets/.../VirtualList.tsx` | Renames `FixedInfiniteVirtualList` to `VariableInfiniteVirtualList` and updates property types for variable size list implementation. |
| `app/client/src/widgets/.../InifiniteScrollBody/index.tsx` | Replaces `FixedInfiniteVirtualList` with `VariableInfiniteVirtualList` in the JSX structure. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
  participant User
  participant BaseVirtualList
  participant useRowHeightMeasurement
  participant useColumnVariableHeight
  participant DOM

  User->>BaseVirtualList: Initiates scroll event
  BaseVirtualList->>useRowHeightMeasurement: Measure row height for visible rows
  BaseVirtualList->>useColumnVariableHeight: Identify columns with variable height
  useRowHeightMeasurement->>DOM: Query for cell dimensions and computed styles
  DOM-->>useRowHeightMeasurement: Return measurement values
  useRowHeightMeasurement->>BaseVirtualList: Return calculated row height
  BaseVirtualList-->>User: Render updated row with dynamic height
```

## Assessment against linked issues

| Objective                                          | Addressed | Explanation |
|----------------------------------------------------|-----------|-------------|
| Support dynamic height content (variable size list) (#39089) | ✅        |             |

## Possibly related PRs
- **appsmithorg/appsmith#39441**: The changes in the main PR, which introduce a new test suite for validating dynamic row heights in a table widget, are related to the modifications in the retrieved PR that enhance the infinite scroll functionality and row management in the `TableWidgetV2`, as both involve handling table row behaviors and rendering.

## Suggested reviewers
- ApekshaBhosale
- rahulbarwal

## Poem
> Rows that grow and then recede,  
> Dynamic heights now take the lead.  
> Virtual lists that smoothly scroll,  
> Testing ensures they meet their goal.  
> Code in motion, shining whole!  
> 🚀✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=appsmithorg/appsmith&utm_content=39646):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-10T08:04:40Z
body: 🔴🔴🔴 Cyclic Dependency Check:

This PR has increased the number of cyclic dependencies by 1, when compared with the release branch.

Refer [this document](https://appsmith.notion.site/How-to-check-cyclic-dependencies-c47b08fe5f2f4261a3a234b19e13f2db) to identify the cyclic dependencies introduced by this PR.

You can view the dependency diff in the [run log](https://github.com/appsmithorg/appsmith/actions/runs/13759612513). Look for the **check-cyclic-dependencies** job in the run.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T08:19:20Z
body: 🔴🔴🔴 Cyclic Dependency Check:

This PR has increased the number of cyclic dependencies by 1, when compared with the release branch.

Refer [this document](https://appsmith.notion.site/How-to-check-cyclic-dependencies-c47b08fe5f2f4261a3a234b19e13f2db) to identify the cyclic dependencies introduced by this PR.

You can view the dependency diff in the [run log](https://github.com/appsmithorg/appsmith/actions/runs/13806643434). Look for the **check-cyclic-dependencies** job in the run.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T10:52:44Z
body: 🔴🔴🔴 Cyclic Dependency Check:

This PR has increased the number of cyclic dependencies by 1, when compared with the release branch.

Refer [this document](https://appsmith.notion.site/How-to-check-cyclic-dependencies-c47b08fe5f2f4261a3a234b19e13f2db) to identify the cyclic dependencies introduced by this PR.

You can view the dependency diff in the [run log](https://github.com/appsmithorg/appsmith/actions/runs/13809532291). Look for the **check-cyclic-dependencies** job in the run.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T11:20:48Z
body: 🔴🔴🔴 Cyclic Dependency Check:

This PR has increased the number of cyclic dependencies by 1, when compared with the release branch.

Refer [this document](https://appsmith.notion.site/How-to-check-cyclic-dependencies-c47b08fe5f2f4261a3a234b19e13f2db) to identify the cyclic dependencies introduced by this PR.

You can view the dependency diff in the [run log](https://github.com/appsmithorg/appsmith/actions/runs/13810040469). Look for the **check-cyclic-dependencies** job in the run.
author_association: NONE
user: login: jacquesikot
created_at: 2025-03-12T12:33:53Z
body: /build-deploy-preview skip-tests=true
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-12T12:34:13Z
body: Deploying Your Preview: <https://github.com/appsmithorg/appsmith/actions/runs/13811463188>.
Workflow: `On demand build Docker image and deploy preview`.
skip-tests: `true`.
env: ``.
PR: 39646.
recreate: .
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T12:44:36Z
body: Deploy-Preview-URL: https://ce-39646.dp.appsmith.com
author_association: NONE
user: login: jacquesikot
created_at: 2025-03-19T11:38:02Z
body: > Does this PR also account for the possibility of multiple columns allowing cell wrapping and HTML content? In such cases, we should ensure that the maximum height is taken into consideration. I haven't explored this in detail, but please ensure that we handle this scenario appropriately.

Yes, this PR check for the highest height for all rows and uses that in the implementation
author_association: CONTRIBUTOR