From 58be1fe33d3cb5a8080aea31651bc70037f2b104 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Wed, 26 Jan 2022 18:19:06 +0100
Subject: [PATCH 1/8] [DataGrid] Add unstable_setRowHeight method to apiRef

---
 .../hooks/features/rows/useGridRowsMeta.ts    | 41 +++++++++++++++----
 .../grid/models/api/gridRowsMetaApi.ts        |  7 ++++
 2 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts
index 0061d1ce85ac4..73d3384c14e31 100644
--- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts
+++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts
@@ -24,7 +24,9 @@ export const useGridRowsMeta = (
   props: Pick<DataGridProcessedProps, 'getRowHeight' | 'pagination' | 'paginationMode'>,
 ): void => {
   const { getRowHeight, pagination, paginationMode } = props;
-  const rowsHeightLookup = React.useRef<{ [key: GridRowId]: number }>({});
+  const rowsHeightLookup = React.useRef<{
+    [key: GridRowId]: { value: number; isResized: boolean };
+  }>({});
   const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector);
   const filterState = useGridSelector(apiRef, gridFilterStateSelector);
   const paginationState = useGridSelector(apiRef, gridPaginationSelector);
@@ -50,14 +52,25 @@ export const useGridRowsMeta = (
       const currentRowHeight = gridDensityRowHeightSelector(state);
       const currentPageTotalHeight = rows.reduce((acc: number, row) => {
         positions.push(acc);
-        let targetRowHeight = currentRowHeight;
 
-        if (getRowHeight) {
-          // Default back to base rowHeight if getRowHeight returns null or undefined.
-          targetRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight;
-        }
+        let targetRowHeight: number;
+
+        if (rowsHeightLookup.current[row.id] && rowsHeightLookup.current[row.id].isResized) {
+          // do not recalculate resized row height and use the value from the lookup
+          targetRowHeight = rowsHeightLookup.current[row.id].value;
+        } else {
+          targetRowHeight = currentRowHeight;
 
-        rowsHeightLookup.current[row.id] = targetRowHeight;
+          if (getRowHeight) {
+            // Default back to base rowHeight if getRowHeight returns null or undefined.
+            targetRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight;
+          }
+
+          rowsHeightLookup.current[row.id] = {
+            value: targetRowHeight,
+            isResized: false,
+          };
+        }
 
         return acc + targetRowHeight;
       }, 0);
@@ -71,7 +84,18 @@ export const useGridRowsMeta = (
   }, [apiRef, pagination, paginationMode, getRowHeight]);
 
   const getTargetRowHeight = (rowId: GridRowId): number =>
-    rowsHeightLookup.current[rowId] || rowHeight;
+    rowsHeightLookup.current[rowId]?.value || rowHeight;
+
+  const setRowHeight = React.useCallback<GridRowsMetaApi['unstable_setRowHeight']>(
+    (id: GridRowId, height: number) => {
+      rowsHeightLookup.current[id] = {
+        value: height,
+        isResized: true,
+      };
+      hydrateRowsMeta();
+    },
+    [hydrateRowsMeta],
+  );
 
   // The effect is used to build the rows meta data - currentPageTotalHeight and positions.
   // Because of variable row height this is needed for the virtualization
@@ -81,6 +105,7 @@ export const useGridRowsMeta = (
 
   const rowsMetaApi: GridRowsMetaApi = {
     unstable_getRowHeight: getTargetRowHeight,
+    unstable_setRowHeight: setRowHeight,
   };
 
   useGridApiMethod(apiRef, rowsMetaApi, 'GridRowsMetaApi');
diff --git a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts
index 1251f19ed9e40..f5d8ced38bcf8 100644
--- a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts
+++ b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts
@@ -11,4 +11,11 @@ export interface GridRowsMetaApi {
    * @ignore - do not document.
    */
   unstable_getRowHeight: (id: GridRowId) => number;
+  /**
+   * Updates the height of a row.
+   * @param {GridRowId} id The id of the row.
+   * @param {number} height The new height.
+   * @ignore - do not document.
+   */
+  unstable_setRowHeight: (id: GridRowId, height: number) => void;
 }

From 038e6114da98f81034b4386f2688341a0eae522f Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Wed, 26 Jan 2022 18:25:38 +0100
Subject: [PATCH 2/8] add story

---
 .../src/stories/grid-rows.stories.tsx         | 66 +++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx
index 3bfeecd50ff8a..3967814bf96c1 100644
--- a/packages/storybook/src/stories/grid-rows.stories.tsx
+++ b/packages/storybook/src/stories/grid-rows.stories.tsx
@@ -6,6 +6,7 @@ import Button from '@mui/material/Button';
 import Popper from '@mui/material/Popper';
 import Paper from '@mui/material/Paper';
 import Box from '@mui/material/Box';
+import TextField from '@mui/material/TextField';
 import {
   GridCellValue,
   GridCellParams,
@@ -18,6 +19,7 @@ import {
   GridEvents,
   MuiEvent,
   GridEventListener,
+  GridSelectionModel,
 } from '@mui/x-data-grid-pro';
 import { useDemoData } from '@mui/x-data-grid-generator';
 import { action } from '@storybook/addon-actions';
@@ -1072,3 +1074,67 @@ export function VariableRowHeight() {
     </div>
   );
 }
+
+export function SetRowHeight() {
+  const { data } = useDemoData({
+    dataSet: 'Commodity',
+    rowLength: 1000,
+  });
+
+  const [selectionModel, setSelectionModel] = React.useState<GridSelectionModel>([]);
+  React.useEffect(() => {
+    if (data.rows.length > 0) {
+      setSelectionModel([data.rows[0].id]);
+    }
+  }, [data.rows]);
+
+  const apiRef = useGridApiRef();
+
+  const handleSubmit = (event: React.SyntheticEvent) => {
+    event.preventDefault();
+    const target = event.target as typeof event.target & {
+      height: { value: string };
+    };
+
+    const height = Number(target.height.value);
+
+    selectionModel.forEach((id) => {
+      apiRef.current.unstable_setRowHeight(id, height);
+    });
+  };
+
+  return (
+    <div style={{ height: 600 }}>
+      <form style={{ display: 'flex', margin: '16px 0' }} onSubmit={handleSubmit}>
+        <TextField
+          name="height"
+          label="Row height"
+          size="small"
+          defaultValue="120"
+          sx={{ mr: 1 }}
+        />
+        <Button type="submit" variant="outlined">
+          Set row height
+        </Button>
+      </form>
+      <DataGridPro
+        {...data}
+        apiRef={apiRef}
+        selectionModel={selectionModel}
+        onSelectionModelChange={(newModel) => setSelectionModel(newModel)}
+        getRowHeight={({ model }) => {
+          if (
+            model.commodity.includes('Oats') ||
+            model.commodity.includes('Milk') ||
+            model.commodity.includes('Soybean') ||
+            model.commodity.includes('Rice')
+          ) {
+            return 80;
+          }
+
+          return null;
+        }}
+      />
+    </div>
+  );
+}

From 0297e8e5a009f4038c52a6b08f0c29f888585f29 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Wed, 26 Jan 2022 18:57:28 +0100
Subject: [PATCH 3/8] add unit tests

---
 .../src/tests/rows.DataGridPro.test.tsx       | 68 ++++++++++++++++++-
 1 file changed, 67 insertions(+), 1 deletion(-)

diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index 53c779a85b361..7168d0f67ad24 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -2,7 +2,13 @@ import * as React from 'react';
 import { createRenderer, fireEvent } from '@mui/monorepo/test/utils';
 import { spy } from 'sinon';
 import { expect } from 'chai';
-import { getCell, getRow, getColumnValues, getRows } from 'test/utils/helperFn';
+import {
+  getCell,
+  getRow,
+  getColumnValues,
+  getRows,
+  getColumnHeaderCell,
+} from 'test/utils/helperFn';
 import {
   GridApiRef,
   GridRowModel,
@@ -792,4 +798,64 @@ describe('<DataGridPro /> - Rows', () => {
       }).not.to.throw();
     });
   });
+
+  describe('apiRef: setRowHeight', () => {
+    const ROW_HEIGHT = 52;
+
+    beforeEach(() => {
+      baselineProps = {
+        autoHeight: isJSDOM,
+        rows: [
+          {
+            id: 0,
+            brand: 'Nike',
+          },
+          {
+            id: 1,
+            brand: 'Adidas',
+          },
+          {
+            id: 2,
+            brand: 'Puma',
+          },
+        ],
+        columns: [{ field: 'brand', headerName: 'Brand' }],
+      };
+    });
+
+    let apiRef: GridApiRef;
+
+    const TestCase = (props: Partial<DataGridProProps>) => {
+      apiRef = useGridApiRef();
+      return (
+        <div style={{ width: 300, height: 300 }}>
+          <DataGridPro {...baselineProps} apiRef={apiRef} rowHeight={ROW_HEIGHT} {...props} />
+        </div>
+      );
+    };
+
+    it('should change row height', () => {
+      render(<TestCase />);
+
+      expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT);
+
+      apiRef.current.unstable_setRowHeight(1, 100);
+      expect(getRow(1).clientHeight).to.equal(100);
+    });
+
+    it('should preserve changed row height after sorting', () => {
+      render(<TestCase />);
+
+      const row = getRow(0);
+      expect(row.clientHeight).to.equal(ROW_HEIGHT);
+
+      apiRef.current.unstable_setRowHeight(0, 100);
+      expect(row.clientHeight).to.equal(100);
+
+      // sort
+      fireEvent.click(getColumnHeaderCell(0));
+
+      expect(row.clientHeight).to.equal(100);
+    });
+  });
 });

From 28320949b3cde6f6ca3e058d2e14aa7c37db23b2 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Wed, 26 Jan 2022 19:38:25 +0100
Subject: [PATCH 4/8] skip tests in jsdom

---
 .../x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx    | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index 7168d0f67ad24..ac9d920012168 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -802,6 +802,13 @@ describe('<DataGridPro /> - Rows', () => {
   describe('apiRef: setRowHeight', () => {
     const ROW_HEIGHT = 52;
 
+    before(function beforeHook() {
+      if (isJSDOM) {
+        // Need layouting
+        this.skip();
+      }
+    });
+
     beforeEach(() => {
       baselineProps = {
         autoHeight: isJSDOM,

From 9702d254ae48352eff6284d36c5937baa39d26b1 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Fri, 28 Jan 2022 11:20:12 +0100
Subject: [PATCH 5/8] check getRowHeight spy in test

---
 .../src/tests/rows.DataGridPro.test.tsx        | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index ac9d920012168..e02497efb0cca 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -811,7 +811,6 @@ describe('<DataGridPro /> - Rows', () => {
 
     beforeEach(() => {
       baselineProps = {
-        autoHeight: isJSDOM,
         rows: [
           {
             id: 0,
@@ -842,27 +841,32 @@ describe('<DataGridPro /> - Rows', () => {
     };
 
     it('should change row height', () => {
+      const RESIZED_ROW_ID = 1;
       render(<TestCase />);
 
       expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT);
 
-      apiRef.current.unstable_setRowHeight(1, 100);
-      expect(getRow(1).clientHeight).to.equal(100);
+      apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100);
+      expect(getRow(RESIZED_ROW_ID).clientHeight).to.equal(100);
     });
 
     it('should preserve changed row height after sorting', () => {
-      render(<TestCase />);
+      const RESIZED_ROW_ID = 0;
+      const getRowHeight = spy();
+      render(<TestCase getRowHeight={getRowHeight} />);
 
-      const row = getRow(0);
+      const row = getRow(RESIZED_ROW_ID);
       expect(row.clientHeight).to.equal(ROW_HEIGHT);
 
-      apiRef.current.unstable_setRowHeight(0, 100);
+      getRowHeight.resetHistory();
+      apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100);
       expect(row.clientHeight).to.equal(100);
 
       // sort
-      fireEvent.click(getColumnHeaderCell(0));
+      fireEvent.click(getColumnHeaderCell(RESIZED_ROW_ID));
 
       expect(row.clientHeight).to.equal(100);
+      expect(getRowHeight.neverCalledWithMatch({ id: RESIZED_ROW_ID })).to.equal(true);
     });
   });
 });

From 9672b2789da09141239d5c98d9690701345791c2 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Tue, 8 Feb 2022 21:07:22 +0100
Subject: [PATCH 6/8] resolve conflicts

---
 .../hooks/features/rows/useGridRowsMeta.ts    | 23 +++++++++++++++----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts
index 8650dce206138..968bacd950d57 100644
--- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts
+++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts
@@ -55,11 +55,21 @@ export const useGridRowsMeta = (
       const currentRowHeight = gridDensityRowHeightSelector(state, apiRef.current.instanceId);
       const currentPageTotalHeight = rows.reduce((acc: number, row) => {
         positions.push(acc);
-        let baseRowHeight = currentRowHeight;
+        let baseRowHeight: number;
 
-        if (getRowHeight) {
-          // Default back to base rowHeight if getRowHeight returns null or undefined.
-          baseRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight;
+        const isResized =
+          (rowsHeightLookup.current[row.id] && rowsHeightLookup.current[row.id].isResized) || false;
+
+        if (isResized) {
+          // do not recalculate resized row height and use the value from the lookup
+          baseRowHeight = rowsHeightLookup.current[row.id].value;
+        } else {
+          baseRowHeight = currentRowHeight;
+
+          if (getRowHeight) {
+            // Default back to base rowHeight if getRowHeight returns null or undefined.
+            baseRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight;
+          }
         }
 
         const heights = apiRef.current.unstable_applyPreProcessors(
@@ -70,7 +80,10 @@ export const useGridRowsMeta = (
 
         const finalRowHeight = Object.values(heights).reduce((acc2, value) => acc2 + value, 0);
 
-        rowsHeightLookup.current[row.id] = baseRowHeight;
+        rowsHeightLookup.current[row.id] = {
+          value: baseRowHeight,
+          isResized,
+        };
 
         return acc + finalRowHeight;
       }, 0);

From b4260cc1b39da8cb52ef97de7473c1d1103e1267 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Thu, 10 Feb 2022 10:48:39 +0100
Subject: [PATCH 7/8] code review: Matheus

---
 .../_modules_/grid/models/api/gridRowsMetaApi.ts |  2 +-
 .../src/tests/rows.DataGridPro.test.tsx          | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts
index d724e53c67a7a..082b14148d94f 100644
--- a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts
+++ b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts
@@ -12,7 +12,7 @@ export interface GridRowsMetaApi {
    */
   unstable_getRowHeight: (id: GridRowId) => number;
   /**
-   * Updates the height of a row.
+   * Updates the base height of a row.
    * @param {GridRowId} id The id of the row.
    * @param {number} height The new height.
    * @ignore - do not document.
diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index e02497efb0cca..fe978eebe9dd0 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -841,32 +841,32 @@ describe('<DataGridPro /> - Rows', () => {
     };
 
     it('should change row height', () => {
-      const RESIZED_ROW_ID = 1;
+      const resizedRowId = 1;
       render(<TestCase />);
 
       expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT);
 
-      apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100);
-      expect(getRow(RESIZED_ROW_ID).clientHeight).to.equal(100);
+      apiRef.current.unstable_setRowHeight(resizedRowId, 100);
+      expect(getRow(resizedRowId).clientHeight).to.equal(100);
     });
 
     it('should preserve changed row height after sorting', () => {
-      const RESIZED_ROW_ID = 0;
+      const resizedRowId = 0;
       const getRowHeight = spy();
       render(<TestCase getRowHeight={getRowHeight} />);
 
-      const row = getRow(RESIZED_ROW_ID);
+      const row = getRow(resizedRowId);
       expect(row.clientHeight).to.equal(ROW_HEIGHT);
 
       getRowHeight.resetHistory();
-      apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100);
+      apiRef.current.unstable_setRowHeight(resizedRowId, 100);
       expect(row.clientHeight).to.equal(100);
 
       // sort
-      fireEvent.click(getColumnHeaderCell(RESIZED_ROW_ID));
+      fireEvent.click(getColumnHeaderCell(resizedRowId));
 
       expect(row.clientHeight).to.equal(100);
-      expect(getRowHeight.neverCalledWithMatch({ id: RESIZED_ROW_ID })).to.equal(true);
+      expect(getRowHeight.neverCalledWithMatch({ id: resizedRowId })).to.equal(true);
     });
   });
 });

From bb2c54d9585b4371bbbdee03b427c776e16e51a6 Mon Sep 17 00:00:00 2001
From: Andrew Cherniavskyi <andrew@mui.com>
Date: Mon, 14 Feb 2022 13:51:22 +0100
Subject: [PATCH 8/8] fix apiRef type

---
 .../grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index c1d883a861848..365e87b791e18 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -829,7 +829,7 @@ describe('<DataGridPro /> - Rows', () => {
       };
     });
 
-    let apiRef: GridApiRef;
+    let apiRef: React.MutableRefObject<GridApi>;
 
     const TestCase = (props: Partial<DataGridProProps>) => {
       apiRef = useGridApiRef();