Skip to content
Merged
1 change: 1 addition & 0 deletions src/components/datagrid/body/data_grid_body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export const EuiDataGridBody: FunctionComponent<EuiDataGridBodyProps> = (
gridRef: gridRef.current,
gridStyles,
columns,
rowHeightsOptions,
});

const { defaultRowHeight, setRowHeight, getRowHeight } = useDefaultRowHeight({
Expand Down
5 changes: 5 additions & 0 deletions src/components/datagrid/data_grid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,11 @@ function moveColumnToIndex(
}

describe('EuiDataGrid', () => {
// Mock requestAnimationFrame to run immediately
jest
.spyOn(window, 'requestAnimationFrame')
.mockImplementation((cb: any) => cb());
Comment on lines +455 to +458
Copy link
Contributor Author

@cee-chen cee-chen Mar 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not totally sure what other options we have, but without this we get a bunch of React async errors on data_grid.test.tsx 😕 (same for setTimeout as well, I tried both)


describe('rendering', () => {
const getBoundingClientRect =
window.Element.prototype.getBoundingClientRect;
Expand Down
7 changes: 1 addition & 6 deletions src/components/datagrid/utils/grid_height_width.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,6 @@ export const useUnconstrainedHeight = ({
}) => {
const { getCorrectRowIndex } = useContext(DataGridSortingContext);

// when a row height is updated, force a re-render of the grid body to update the unconstrained height
const forceRender = useForceRender();
useEffect(() => {
rowHeightUtils.setRerenderGridBody(forceRender);
}, [rowHeightUtils, forceRender]);

let knownHeight = 0; // tracks the pixel height of rows we know the size of
let knownRowCount = 0; // how many rows we know the size of
for (let i = startRow; i < endRow; i++) {
Expand Down Expand Up @@ -137,6 +131,7 @@ export const useUnconstrainedHeight = ({
innerGridRef.current,
'width'
);
const forceRender = useForceRender();
useUpdateEffect(forceRender, [innerWidth]);

const unconstrainedHeight =
Expand Down
109 changes: 108 additions & 1 deletion src/components/datagrid/utils/row_heights.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
* Side Public License, v 1.
*/

import { RowHeightUtils, cellPaddingsMap } from './row_heights';
import { act } from 'react-dom/test-utils';
import { testCustomHook } from '../../../test/test_custom_hook.test_helper';
import { startingStyles } from '../controls';

import {
RowHeightUtils,
cellPaddingsMap,
useRowHeightUtils,
} from './row_heights';

describe('RowHeightUtils', () => {
const rowHeightUtils = new RowHeightUtils();
Expand Down Expand Up @@ -147,6 +155,16 @@ describe('RowHeightUtils', () => {
});
});
});

it('falls back to m-sized cellPadding if gridStyle.cellPadding is undefined', () => {
rowHeightUtils.cacheStyles({ cellPadding: undefined });

// @ts-ignore this var is private, but we're inspecting it for the sake of the unit test
expect(rowHeightUtils.styles).toEqual({
paddingTop: 6,
paddingBottom: 6,
});
});
});

describe('getStylesForCell (returns inline CSS styles based on height config)', () => {
Expand Down Expand Up @@ -371,3 +389,92 @@ describe('RowHeightUtils', () => {
});
});
});

describe('useRowHeightUtils', () => {
const mockArgs = {
gridRef: null,
gridStyles: startingStyles,
columns: [{ id: 'A' }, { id: 'B' }],
rowHeightOptions: undefined,
};

it('instantiates and returns an instance of RowHeightUtils', () => {
const { return: rowHeightUtils } = testCustomHook(() =>
useRowHeightUtils(mockArgs)
);
expect(rowHeightUtils).toBeInstanceOf(RowHeightUtils);
});

it('populates internal RowHeightUtils vars from outside dependencies', () => {
const args = { ...mockArgs, gridRef: {} as any };
const { return: rowHeightUtils } = testCustomHook(() =>
useRowHeightUtils(args)
);
// @ts-ignore - intentionally inspecting private var for test
expect(rowHeightUtils.grid).toEqual(args.gridRef);
// @ts-ignore - intentionally inspecting private var for test
expect(rowHeightUtils.rerenderGridBody).toBeInstanceOf(Function);
});

it('forces a rerender every time rowHeightsOptions changes', () => {
const requestAnimationFrameSpy = jest
.spyOn(window, 'requestAnimationFrame')
.mockImplementation((cb: any) => cb());

const { updateHookArgs } = testCustomHook(useRowHeightUtils, mockArgs);
expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(1);

updateHookArgs({ rowHeightsOptions: { defaultHeight: 300 } });
expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(2);

updateHookArgs({
rowHeightsOptions: { defaultHeight: 300, rowHeights: { 0: 200 } },
});
expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(3);
});

it('updates internal cached styles whenever gridStyle.cellPadding changes', () => {
const { return: rowHeightUtils, updateHookArgs } = testCustomHook(
useRowHeightUtils,
mockArgs
);

updateHookArgs({ gridStyles: { ...startingStyles, cellPadding: 's' } });
// @ts-ignore - intentionally inspecting private var for test
expect(rowHeightUtils.styles).toEqual({
paddingTop: 4,
paddingBottom: 4,
});
});

it('prunes columns from the row heights cache if a column is hidden', () => {
const { return: rowHeightUtils, updateHookArgs } = testCustomHook<
RowHeightUtils
>(useRowHeightUtils, mockArgs);

act(() => {
rowHeightUtils.setRowHeight(0, 'A', 30, 0);
rowHeightUtils.setRowHeight(0, 'B', 50, 0);
});
// @ts-ignore - intentionally inspecting private var for test
expect(rowHeightUtils.heightsCache).toMatchInlineSnapshot(`
Map {
0 => Map {
"A" => 42,
"B" => 62,
},
}
`);

updateHookArgs({ columns: [{ id: 'A' }] }); // Hiding column B

// @ts-ignore - intentionally inspecting private var for test
expect(rowHeightUtils.heightsCache).toMatchInlineSnapshot(`
Map {
0 => Map {
"A" => 42,
},
}
`);
});
});
33 changes: 27 additions & 6 deletions src/components/datagrid/utils/row_heights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from 'react';
import type { VariableSizeGrid as Grid } from 'react-window';
import { isObject, isNumber } from '../../../services/predicate';
import { useForceRender } from '../../../services';
import {
EuiDataGridStyleCellPaddings,
EuiDataGridStyle,
Expand Down Expand Up @@ -96,8 +97,8 @@ export class RowHeightUtils {

cacheStyles(gridStyles: EuiDataGridStyle) {
this.styles = {
paddingTop: cellPaddingsMap[gridStyles.cellPadding!],
paddingBottom: cellPaddingsMap[gridStyles.cellPadding!],
paddingTop: cellPaddingsMap[gridStyles.cellPadding || 'm'],
paddingBottom: cellPaddingsMap[gridStyles.cellPadding || 'm'],
};
}

Expand Down Expand Up @@ -201,6 +202,9 @@ export class RowHeightUtils {
rowHeights.set(colId, adaptedHeight);
this.heightsCache.set(rowIndex, rowHeights);
this.resetRow(visibleRowIndex);

// When an auto row height is updated, force a re-render
// of the grid body to update the unconstrained height
this.rerenderGridBody();
}

Expand Down Expand Up @@ -246,24 +250,41 @@ export class RowHeightUtils {
}

/**
* Hook for instantiating RowHeightUtils, and also updating
* internal vars from outside props via useEffects
* Hook for instantiating RowHeightUtils, setting internal class vars,
* and setting up various row-height-related side effects
*/
export const useRowHeightUtils = ({
gridRef,
gridStyles,
columns,
rowHeightsOptions,
}: {
gridRef: Grid | null;
gridStyles: EuiDataGridStyle;
columns: EuiDataGridColumn[];
rowHeightsOptions?: EuiDataGridRowHeightsOptions;
}) => {
const rowHeightUtils = useMemo(() => new RowHeightUtils(), []);

// Update rowHeightUtils with grid ref
// Update rowHeightUtils with internal vars from outside dependencies
const forceRender = useForceRender();
useEffect(() => {
if (gridRef) rowHeightUtils.setGrid(gridRef);
}, [gridRef, rowHeightUtils]);
rowHeightUtils.setRerenderGridBody(forceRender);
}, [gridRef, forceRender, rowHeightUtils]);

// Forces a rerender whenever the row heights change, as this can cause the
// grid to change height/have scrollbars. Without this, grid rerendering is stale
useEffect(() => {
requestAnimationFrame(forceRender);
}, [
// Effects that should cause rerendering
rowHeightsOptions?.defaultHeight,
rowHeightsOptions?.rowHeights,
// Dependencies
rowHeightUtils,
forceRender,
]);

// Re-cache styles whenever grid density changes
useEffect(() => {
Expand Down
3 changes: 3 additions & 0 deletions upcoming_changelogs/5712.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed EuiDataGrid not rerendering correctly on row heights change