레퍼런스
Hook 레퍼런스
useAsyncDataSource, useColumnSort, useMovableColumns 등 Hook의 시그니처와 사용 예제
reopt design업데이트
1. useAsyncDataSource
대규모 데이터를 페이지 단위로 지연 로딩합니다. 뷰포트에 보이는 영역 기준으로 필요한 페이지만 자동으로 요청합니다.
Options
| 속성 | 타입 | 설명 |
|---|---|---|
rowCount | number | 전체 행 수 (서버에서 제공) |
pageSize | number | 한 페이지당 행 수 (기본 200) |
preloadPages | number | 현재 페이지 앞/뒤로 미리 로드할 페이지 수 (기본 1) |
getRowData | (range) => Promise<Row[]> | 페이지 데이터를 비동기로 가져오는 함수 |
makePlaceholderRow | (index) => Row | 로딩 중 표시할 플레이스홀더 행 생성 |
Returns
| 속성 | 타입 | 설명 |
|---|---|---|
rows | Row[] | 전체 길이 배열 (로딩 안 된 행은 placeholder) |
onVisibleRegionChanged | callback | DataGrid의 onVisibleRegionChanged에 연결 |
updateRow | (index, row) => void | 특정 행 데이터를 수동 업데이트 |
isRowLoaded | (index) => boolean | 해당 행이 로드 완료인지 확인 |
tsx
"use client";
import {
DataGrid,
useAsyncDataSource,
type DataGridColumn,
} from "@reopt-ai/opt-datagrid";
interface Order {
id: string;
product: string;
amount: number;
}
const columns: DataGridColumn<Order>[] = [
{ id: "product", title: "상품", getValue: (r) => r.product, width: 200 },
{ id: "amount", title: "금액", getValue: (r) => r.amount, width: 120 },
];
export default function AsyncGrid() {
const { rows, onVisibleRegionChanged } = useAsyncDataSource<Order>({
rowCount: 50_000,
pageSize: 200,
preloadPages: 1,
getRowData: async ({ offset, limit }) => {
const res = await fetch(`/api/orders?offset=${offset}&limit=${limit}`);
return res.json();
},
makePlaceholderRow: (index) => ({
id: `placeholder-${index}`,
product: "로딩 중...",
amount: 0,
}),
});
return (
<DataGrid
columns={columns}
rows={rows}
getRowId={(r) => r.id}
onVisibleRegionChanged={onVisibleRegionChanged}
height={600}
/>
);
}2. useColumnSort
컬럼 헤더 클릭으로 정렬 상태를 관리합니다. 3가지 정렬 모드를 지원합니다.
| mode | 동작 |
|---|---|
default | getValue 반환값 기준 기본 비교 (문자열/숫자 자동 판별) |
raw | 원본 Row 객체를 직접 비교 함수에 전달 |
smart | 숫자 접두어, 날짜 문자열 등을 자동 인식하여 자연 정렬 |
tsx
import { DataGrid, useColumnSort } from "@reopt-ai/opt-datagrid";
function SortableGrid({ initialRows, columns }) {
const [sort, setSort] = useState<{
column: string;
direction: "asc" | "desc";
}>();
const { rows, getOriginalIndex } = useColumnSort({
rows: initialRows,
columns,
sort: sort
? { column: sort.column, direction: sort.direction, mode: "smart" }
: undefined,
});
return (
<DataGrid
columns={columns}
rows={rows}
height={400}
onHeaderClick={(columnId) => {
setSort((prev) => {
if (prev?.column === columnId) {
return prev.direction === "asc"
? { column: columnId, direction: "desc" }
: undefined; // 세 번째 클릭: 정렬 해제
}
return { column: columnId, direction: "asc" };
});
}}
/>
);
}3. useMovableColumns
드래그 앤 드롭으로 컬럼 순서를 변경합니다. 변경된 순서를 localStorage 등에 저장하면 사용자별 레이아웃 유지가 가능합니다.
tsx
import {
DataGrid,
useMovableColumns,
type DataGridColumn,
} from "@reopt-ai/opt-datagrid";
function ReorderableGrid({ baseColumns, rows }) {
const { columns, moveColumn, resetOrder } = useMovableColumns({
columns: baseColumns,
onColumnMoved: (fromIndex, toIndex) => {
// 컬럼 순서를 localStorage에 저장
const ids = columns.map((c) => c.id);
const moved = [...ids];
const [item] = moved.splice(fromIndex, 1);
moved.splice(toIndex, 0, item);
localStorage.setItem("column-order", JSON.stringify(moved));
},
});
return (
<>
<button onClick={resetOrder}>순서 초기화</button>
<DataGrid
columns={columns}
rows={rows}
height={400}
onColumnMoved={moveColumn}
/>
</>
);
}4. useCollapsingGroups
컬럼 그룹 헤더를 클릭하면 해당 그룹의 컬럼을 접을 수 있습니다. 접힌 그룹은 지정된 너비로 축소됩니다.
tsx
import {
DataGrid,
useCollapsingGroups,
} from "@reopt-ai/opt-datagrid";
function CollapsibleGroupGrid({ baseColumns, rows }) {
const {
columns,
collapsedGroups,
onGroupHeaderClicked,
setCollapsedGroups,
} = useCollapsingGroups({
columns: baseColumns,
collapsedGroups: new Set(), // 초기 접힘 상태
collapsedWidth: 40, // 접힌 컬럼 너비 (px)
});
return (
<>
<button onClick={() => setCollapsedGroups(new Set())}>
모든 그룹 펼치기
</button>
<DataGrid
columns={columns}
rows={rows}
height={400}
onGroupHeaderClicked={onGroupHeaderClicked}
/>
</>
);
}5. useDataGridRemoteDataSource
서버와의 전체 CRUD 연동을 위한 프로토콜 Hook입니다. 뷰 열기/닫기, 행 로딩, 편집 저장, 실시간 무효화 구독까지 하나의 계약으로 다룹니다.
Protocol 콜백
| 콜백 | 설명 |
|---|---|
openView | 뷰 초기화. rowCount, 초기 정렬/필터 설정 반환 |
closeView | 뷰 정리. 구독 해제, 리소스 해제 |
loadRows | offset/limit 범위의 행 데이터를 서버에서 로드 |
saveEdits | 편집된 셀 배치를 서버에 저장. 실패/거부 셀 목록 반환 |
subscribeToInvalidations | WebSocket 등으로 실시간 행 무효화 알림 수신 |
Returns
| 속성 | 설명 |
|---|---|
isViewReady | 뷰 초기화 완료 여부 |
rows | 현재 로드된 행 배열 (placeholder 포함) |
failedEdits | 네트워크 오류로 저장 실패한 편집 목록 |
rejectedEdits | 서버가 거부한 편집 목록 (비즈니스 검증 실패) |
telemetry | 로드/저장 요청 수, 평균 레이턴시 등 통계 |
tsx
import {
DataGrid,
useDataGridRemoteDataSource,
} from "@reopt-ai/opt-datagrid";
function RemoteGrid({ viewId }: { viewId: string }) {
const {
rows,
isViewReady,
failedEdits,
onVisibleRegionChanged,
onRowsChange,
} = useDataGridRemoteDataSource({
openView: async () => {
const meta = await fetch(`/api/views/${viewId}`).then((r) => r.json());
return { rowCount: meta.totalRows };
},
closeView: async () => {
// 정리 작업
},
loadRows: async ({ offset, limit }) => {
return fetch(
`/api/views/${viewId}/rows?offset=${offset}&limit=${limit}`
).then((r) => r.json());
},
saveEdits: async (edits) => {
const res = await fetch(`/api/views/${viewId}/edits`, {
method: "POST",
body: JSON.stringify(edits),
});
return res.json(); // { failed: [], rejected: [] }
},
subscribeToInvalidations: (onInvalidate) => {
const ws = new WebSocket(`ws://api/views/${viewId}/live`);
ws.onmessage = (e) => onInvalidate(JSON.parse(e.data));
return () => ws.close();
},
});
if (!isViewReady) return <div>뷰 로딩 중...</div>;
return (
<>
{failedEdits.length > 0 && (
<div className="text-sm text-red-600">
{failedEdits.length}건 저장 실패
</div>
)}
<DataGrid
columns={columns}
rows={rows}
height={600}
onVisibleRegionChanged={onVisibleRegionChanged}
onRowsChange={onRowsChange}
/>
</>
);
}6. useDataGridUndoRedo
편집 히스토리를 배치(batch) 단위로 관리합니다. Ctrl+Z / Ctrl+Y 키보드 단축키와 자동 연동됩니다.
| 속성 | 타입 | 설명 |
|---|---|---|
canUndo | boolean | 실행 취소 가능 여부 |
canRedo | boolean | 다시 실행 가능 여부 |
pushBatch | (batch) => void | 편집 배치를 히스토리에 푸시 |
popUndo | () => Batch | undefined | 마지막 편집 되돌리기 |
popRedo | () => Batch | undefined | 되돌린 편집 다시 실행 |
clear | () => void | 전체 히스토리 초기화 |
tsx
import {
DataGrid,
useDataGridUndoRedo,
} from "@reopt-ai/opt-datagrid";
function UndoableGrid({ columns, initialRows }) {
const [rows, setRows] = useState(initialRows);
const { canUndo, canRedo, pushBatch, popUndo, popRedo, clear } =
useDataGridUndoRedo();
const handleRowsChange = (newRows, changes) => {
// 변경 배치를 히스토리에 기록
pushBatch({
previousRows: rows,
newRows,
changes,
});
setRows(newRows);
};
const handleUndo = () => {
const batch = popUndo();
if (batch) setRows(batch.previousRows);
};
const handleRedo = () => {
const batch = popRedo();
if (batch) setRows(batch.newRows);
};
return (
<>
<div className="flex gap-2">
<button onClick={handleUndo} disabled={!canUndo}>Undo</button>
<button onClick={handleRedo} disabled={!canRedo}>Redo</button>
<button onClick={clear}>히스토리 초기화</button>
</div>
<DataGrid
columns={columns}
rows={rows}
height={400}
onRowsChange={handleRowsChange}
/>
</>
);
}7. useDataGridController
외부에서 DataGrid에 명령(command)을 디스패치합니다. 프로그래밍 방식으로 스크롤, 선택, 편집 등을 제어할 때 사용합니다.
| Command | 설명 |
|---|---|
scrollTo | 지정한 행/열 위치로 스크롤 |
setSelection | 프로그래밍 방식으로 선택 영역 설정 |
startEdit | 지정 셀의 편집 모드 시작 |
appendRow | 새 행 추가 후 첫 번째 편집 가능 셀로 포커스 |
copySelection | 현재 선택 영역을 클립보드에 복사 |
focus | DataGrid에 키보드 포커스 설정 |
tsx
import {
DataGrid,
createDataGridController,
useDataGridController,
} from "@reopt-ai/opt-datagrid";
function ControlledGrid({ columns, rows }) {
const controller = useDataGridController();
return (
<>
<div className="flex gap-2">
<button
onClick={() =>
controller.dispatch({ type: "scrollTo", row: 0, column: 0 })
}
>
맨 위로
</button>
<button
onClick={() =>
controller.dispatch({
type: "setSelection",
selection: { start: { row: 0, col: 0 }, end: { row: 4, col: 2 } },
})
}
>
처음 5행 선택
</button>
<button
onClick={() =>
controller.dispatch({
type: "appendRow",
row: { id: crypto.randomUUID(), name: "", value: 0 },
})
}
>
행 추가
</button>
</div>
<DataGrid
columns={columns}
rows={rows}
height={400}
controller={controller}
/>
</>
);
}