Responsive Containers (DataGridContainer)
Measure the grid container with ResizeObserver so the DataGrid renders at the correct size as its layout grows or shrinks.
import * as React from 'react';
import { BGrid } from 'beautiful-grid';
import type { BGridColumn, BGridDataItem } from 'beautiful-grid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
interface Order {
id: string;
customer: string;
status: '준비 중' | '배송 중' | '완료';
amount: number;
}
const columns: BGridColumn<Order>[] = [
{ key: 'id', label: '주문 번호', width: 120 },
{ key: 'customer', label: '고객', width: 180 },
{ key: 'status', label: '상태', width: 110, align: 'center' },
{ key: 'amount', label: '금액', width: 140, align: 'right', itemRender: ({ value }) => `${value.toLocaleString()}원` },
];
const data: BGridDataItem<Order>[] = Array.from({ length: 80 }, (_, index) => ({
values: {
id: `ORDER-${String(index + 1).padStart(4, '0')}`,
customer: `고객 ${index + 1}`,
status: ['준비 중', '배송 중', '완료'][index % 3] as Order['status'],
amount: 18000 + index * 750,
},
}));
export default function ContainerResizeExample() {
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
const containerRef = React.useRef<HTMLDivElement>(null);
const { width, height } = useContainerSize(containerRef);
return (
<div>
<div style={{ alignItems: 'center', display: 'flex', gap: 12, marginBottom: 12 }}>
<button
type="button"
onClick={() => setIsSidebarOpen(open => !open)}
style={{ background: '#2563eb', border: 0, borderRadius: 6, color: '#fff', cursor: 'pointer', padding: '8px 12px' }}
>
{isSidebarOpen ? '사이드 패널 닫기' : '사이드 패널 열기'}
</button>
<span style={{ color: '#64748b', fontSize: 13 }}>컨테이너: {Math.round(width)} × {Math.round(height)}px</span>
</div>
<div
style={{
display: 'grid',
gap: 12,
gridTemplateColumns: isSidebarOpen ? 'minmax(150px, 0.35fr) minmax(0, 1fr)' : 'minmax(0, 1fr)',
height: 440,
}}
>
{isSidebarOpen && (
<aside style={{ background: '#f1f5f9', borderRadius: 8, color: '#475569', padding: 16 }}>
<strong style={{ display: 'block', marginBottom: 8 }}>필터 패널</strong>
화면 폭이 줄어도 그리드는 이 영역의 정확한 크기를 다시 측정합니다.
</aside>
)}
<DataGridContainer ref={containerRef} style={{ height: '100%', minWidth: 0 }}>
<BGrid<Order> width={width} height={height} columns={columns} data={data} />
</DataGridContainer>
</div>
</div>
);
}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,
};
}1. Measure the grid, not the viewport
Application layouts often change width and height at runtime because of side panels, tabs, and split views. If you give the grid an initial window size or a fixed value, it may look correct when the viewport grows but retain a stale size when the viewport shrinks, causing horizontal scrolling and layout misalignment.
useContainerSize uses ResizeObserver to observe the actual content area of the element that contains the grid. Pass the measured width and height to BGrid so it rerenders whenever the parent layout grows or shrinks.
2. Why DataGridContainer is necessary
DataGridContainer is a position: relative measurement boundary. The DataGrid root inside it is positioned with position: absolute; inset: 0. The container therefore retains its exact size in normal document flow while the grid fills the measured area completely.
In flex and grid layouts, the item that contains the grid needs min-width: 0 so it can shrink below its content’s intrinsic width. The container also needs an explicit height. DataGrid uses that height to calculate the visible row count and virtual-scroll range.
3. Setup steps
- Wrap the grid area in
DataGridContainerand give it a height. - Pass the container ref to
useContainerSize. - Pass the returned
widthandheighttoBGrid. - If the parent uses flexbox or CSS Grid, apply
minWidth: 0or CSSmin-width: 0to the item that contains the grid.
Open and close the side panel in the live example. The grid immediately adjusts to the correct width whether the container grows or shrinks.
4. Approaches to avoid
- Reading
window.innerWidthonce and using it as the grid size - Passing only
height="100%"without giving the container a definite height - Leaving the default
min-width: autoon a flex item so its content cannot shrink - Measuring an arbitrary DOM element outside the grid and sharing that size across multiple grids
The most predictable approach is for each grid to observe its own container. This remains reliable in split views, collapsible panels, and responsive layouts.