Variable Row Height
Use getRowHeight to assign data-driven row heights while preserving virtual-scroll performance.
import * as React from 'react';
import { BGrid, type BGridColumn, type BGridDataItem } from 'beautiful-grid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
import { t } from './i18n';
type ActivityKind = 'milestone' | 'update' | 'note';
interface ProjectActivity {
id: number;
kind: ActivityKind;
title: string;
owner: string;
status: string;
rowHeight: number;
}
const kindLabels: Record<ActivityKind, string> = {
milestone: t('마일스톤', 'Milestone'),
update: t('업데이트', 'Update'),
note: t('메모', 'Note'),
};
const activities: BGridDataItem<ProjectActivity>[] = Array.from({ length: 200 }, (_, index) => {
const kind: ActivityKind = index % 12 === 0 ? 'milestone' : index % 4 === 0 ? 'update' : 'note';
const rowHeight = kind === 'milestone' ? 52 : kind === 'update' ? 40 : 29;
return {
values: {
id: index + 1,
kind,
title:
kind === 'milestone'
? t(`스프린트 ${Math.floor(index / 12) + 1} 완료`, `Sprint ${Math.floor(index / 12) + 1} completed`)
: t(`프로젝트 활동 ${index + 1}`, `Project activity ${index + 1}`),
owner: ['Mina', 'Daniel', 'Sora', 'Alex'][index % 4],
status:
kind === 'milestone'
? t('완료', 'Done')
: index % 3 === 0
? t('검토 중', 'In review')
: t('진행 중', 'In progress'),
rowHeight,
},
};
});
const columns: BGridColumn<ProjectActivity>[] = [
{ id: 'id', key: 'id', label: '#', width: 70, align: 'center' },
{
id: 'kind',
key: 'kind',
label: t('유형', 'Type'),
width: 110,
itemRender: ({ values }) => <strong>{kindLabels[values.kind]}</strong>,
},
{ id: 'title', key: 'title', label: t('활동', 'Activity'), width: 260 },
{ id: 'owner', key: 'owner', label: t('담당자', 'Owner'), width: 100 },
{ id: 'status', key: 'status', label: t('상태', 'Status'), width: 110 },
{
id: 'rowHeight',
key: 'rowHeight',
label: t('행 높이', 'Row height'),
width: 100,
align: 'right',
itemRender: ({ value }) => `${value}px`,
},
];
export default function VariableRowHeightExample() {
const containerRef = React.useRef<HTMLDivElement>(null);
const { width, height } = useContainerSize(containerRef);
const getRowHeight = React.useCallback((row: ProjectActivity) => row.rowHeight, []);
return (
<DataGridContainer ref={containerRef} className='variable-row-height-example'>
<BGrid<ProjectActivity>
width={width}
height={height}
columns={columns}
data={activities}
rowKey='id'
showLineNumber
frozenRowCount={1}
getRowHeight={getRowHeight}
/>
</DataGridContainer>
);
}import * as React from 'react';
import './DataGridContainer.css';
interface DataGridContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
}
/**
* Keeps a DataGrid in a measured, fixed layout box.
*
* BGrid's rendered root is absolutely positioned within this relative
* container. This makes a ResizeObserver measurement authoritative when a
* surrounding flex or grid layout shrinks as well as when it expands.
*/
const DataGridContainer = React.forwardRef<HTMLDivElement, DataGridContainerProps>(
({ className, ...rest }, ref) => (
<div ref={ref} className={`data-grid-container ${className ?? ''}`.trim()} {...rest} />
),
);
DataGridContainer.displayName = 'DataGridContainer';
export default DataGridContainer;.data-grid-container {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
font-size: 13px;
}
.data-grid-container > .bgrid-root {
position: absolute;
inset: 0;
}import * as React from 'react';
export function useContainerSize(ref: React.MutableRefObject<HTMLElement | null>, additionalDeps: unknown[] = []) {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const resizeObserver = React.useRef(
new ResizeObserver(entries => {
if (entries.length !== 1) {
throw new Error('Invalid Container length');
}
const [entry] = entries;
const { width, height } = entry.contentRect;
setWidth(width);
setHeight(height);
}),
);
React.useEffect(() => {
if (!ref.current) return;
const observer = resizeObserver.current;
const element = ref.current;
setWidth(element.clientWidth);
setHeight(element.clientHeight);
observer.observe(element);
return () => {
observer.unobserve(element);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...additionalDeps, ref]);
return {
width,
height,
};
}Assign heights from row data
getRowHeight(row, index) receives the row values and its current displayed index, then returns the complete row height in pixels. The live example uses 52px for milestones, 40px for updates, and 29px for ordinary notes.
const getRowHeight = React.useCallback((row: ProjectActivity) => {
if (row.kind === 'milestone') return 52;
if (row.kind === 'update') return 40;
return 29;
}, []);
<BGrid
data={activities}
columns={columns}
getRowHeight={getRowHeight}
/>
The callback receives item.values, not the BGridDataItem wrapper. After sorting or filtering, index follows the resulting display order.
Performance behavior
BeautifulGrid calculates heights and cumulative offsets once whenever the displayed data or getRowHeight reference changes. Scrolling reads the cache and locates the first row with binary search, so it does not repeatedly invoke the callback. Keep the function reference stable with useCallback, as shown above, to avoid unnecessary cache rebuilds.
Automatic DOM measurement for content-based heights is not supported. If getRowHeight is omitted or returns zero, a negative number, NaN, or Infinity, the grid falls back to itemHeight + itemPadding * 2.