그리드 사용 예제
dictionary-app은 ag-grid를 용도별로 세 가지 공용 컴포넌트로 감싸서 쓴다 — 서버사이드 페이지네이션이 있는
DataGrid, 엑셀 시트처럼 보이는 ExcelGrid, 모달 안에 들어가는 ModalGrid. 모듈 등록/기본 테마는
DataGrid가 쓰는 agGridTheme.ts가 앱 시작 시 한 번만 수행하고, 나머지 둘은 그 위에 테마만 겹쳐 쓴다.
DataGrid
Section titled “DataGrid”domain 페이지를 예시로, 서버사이드 페이지네이션 컨트롤까지 한 번에 연결하는 방법을 정리한다.
모듈 등록과 테마
Section titled “모듈 등록과 테마”- AG Grid v33+부터는 필요한 기능을 모듈 단위로 등록해야 동작한다. 그리드를 렌더링하기 전에 한 번만
실행되면 되므로, 앱 진입 파일에서 이 파일이 처음 import되는 시점에 부수효과로 등록해둔다.
utils/agGridTheme.ts import {AllCommunityModule, ModuleRegistry, themeQuartz} from 'ag-grid-community'ModuleRegistry.registerModules([AllCommunityModule])export const agGridTheme = themeQuartz.withParams({accentColor: '#aa3bff',borderRadius: 6,fontFamily: "'Pretendard', sans-serif",})
DataGrid 공용 컴포넌트
Section titled “DataGrid 공용 컴포넌트”rowData/columnDefs는 ag-grid 그대로 받고, 서버사이드 페이지네이션 정보(pagination)와 페이지 변경 콜백(handlePageChange)을 추가로 받아 하단 페이지네이션 컨트롤까지 함께 그린다.components/organisms/data-grid/DataGrid.tsx 6 collapsed linesimport {useEffect, useRef} from 'react'import {Box, VStack} from '@chakra-ui/react'import {AgGridReact} from 'ag-grid-react'import {GridEmptyState, Pagination} from '@/components'import {agGridTheme} from '@/utils'export const DataGrid = <T,>({rowData,columnDefs,pagination,handlePageChange,height = '400px',showPagination = true,noDataMessage,...agGridProps}: DataGridProps<T>) => {const gridRef = useRef<AgGridReact<T>>(null)useEffect(() => {gridRef.current?.api?.refreshCells({force: true})}, [pagination.pageNumber])return (<VStack align="stretch" gap={showPagination ? 3 : 0}><Box h={height}><AgGridReact<T>ref={gridRef}theme={agGridTheme}rowData={rowData}columnDefs={columnDefs}defaultColDef={{sortable: false}}noRowsOverlayComponent={GridEmptyState}noRowsOverlayComponentParams={{message: noDataMessage}}{...agGridProps}/></Box>{showPagination && (<Pagination pagination={pagination} handlePageChange={handlePageChange} />)}</VStack>)}AgGridReactProps의pagination(boolean, AG Grid 자체 클라이언트 페이지네이션)은 이 컴포넌트가 쓰는 서버사이드pagination(객체)과 이름이 겹치고 용도도 달라Omit으로 막아뒀다 — AG Grid 자체 페이지네이션은 쓰지 않고 항상rowData로 넘어온 현재 페이지 데이터만 그린다.defaultColDef={{sortable: false}}로 모든 컬럼의 정렬을 기본으로 꺼둔다 —rowData가 서버 페이지네이션 결과(현재 페이지 몇십 건)뿐이라, 클라이언트 정렬을 켜두면 “그 페이지 안에서만” 재정렬될 뿐인데 전체 목록 기준 정렬처럼 보이는 문제가 있다.- 페이지가 바뀔 때마다
refreshCells({force: true})로 모든 셀을 강제로 다시 계산한다 — No.처럼 행 데이터 자체가 아니라pagination(외부 상태)으로 계산되는 값은 ag-grid의 기본 변경 감지에 걸리지 않기 때문이다.
사용 예: 서버사이드 페이지네이션 그리드
Section titled “사용 예: 서버사이드 페이지네이션 그리드”columnDefs를 정의한다 — No. 컬럼은 실제 필드가 아니라pagination.pageNumber/pageSize와 행 순서(node.rowIndex)로 계산한 일련번호다.pages/word/domain/useDomain.ts const columnDefs = useMemo<ColDef<DomainResponseDto>[]>(() => [{colId: 'no',headerName: 'No.',width: 90,sortable: false,filter: false,valueGetter: ({node}) =>pagination.pageNumber * pagination.pageSize + (node?.rowIndex ?? 0) + 1,},{field: 'name', headerName: '이름'},{field: 'description', headerName: '상세설명', flex: 1},// ...],[pagination],)- 조회 훅이 반환하는
{domains, pagination, handlePageChange}를DataGrid에 그대로 연결한다(조회 훅 구현은 백엔드 API 사용 예제의queries항목 참고).pages/word/domain/Domain.tsx 6 collapsed linesimport type {RowSelectionOptions} from 'ag-grid-community'import type {components} from '@/types'import {DataGrid} from '@/components'import {useDomain} from './useDomain'type DomainResponseDto = components['schemas']['DomainResponseDto']const ROW_SELECTION: RowSelectionOptions<DomainResponseDto> = {mode: 'multiRow',checkboxes: true,headerCheckbox: true,}export const Domain = () => {const {domains, pagination, handlePageChange, isDomainsLoading, columnDefs} = useDomain()return (<DataGrid<DomainResponseDto>rowData={domains}columnDefs={columnDefs}pagination={pagination}handlePageChange={handlePageChange}loading={isDomainsLoading}height="480px"rowSelection={ROW_SELECTION}onSelectionChanged={handleSelectionChanged}onRowDoubleClicked={handleRowDoubleClick}/>)} - 행 선택이 필요하면
rowSelection(ag-grid 옵션)을 그대로 전달한다 —DataGrid가 감싸지 않은 ag-grid 표준 prop은...agGridProps로 그대로 통과된다.
ExcelGrid
Section titled “ExcelGrid”DataGrid와 달리 서버사이드 페이지네이션 컨트롤이 없다 — 엑셀 시트는 페이지가 아니라 스크롤로 데이터를
넘기는 게 자연스러워서, rowData를 그대로 넘기고 그리드 자체 스크롤로 나머지를 보게 한다.
agGridTheme대신excelGridTheme을 적용한다 — 모듈 등록은agGridTheme.ts가 이미 했으므로 여기서 다시 하지 않는다.components/organisms/excel-grid/ExcelGrid.tsx 5 collapsed linesimport {Box} from '@chakra-ui/react'import {AgGridReact} from 'ag-grid-react'import {GridEmptyState} from '@/components'import {excelGridTheme} from '@/utils'export const ExcelGrid = <T,>({rowData,columnDefs,height = '400px',noDataMessage,...agGridProps}: ExcelGridProps<T>) => {return (<Box h={height}><AgGridReact<T>theme={excelGridTheme}rowData={rowData}columnDefs={columnDefs}noRowsOverlayComponent={GridEmptyState}noRowsOverlayComponentParams={{message: noDataMessage}}{...agGridProps}/></Box>)}excelGridTheme은agGridTheme을 상속하지 않고themeQuartz에서 직접 새로 만든다 — 격자선 (rowBorder/columnBorder+ 헤더 버전)을 모두 켜고, 폰트/행 높이를 줄이고, 모서리를 각지게 (borderRadius: 0) 만들어 엑셀 시트 느낌을 낸다.utils/excelGridTheme.ts export const excelGridTheme = themeQuartz.withParams({accentColor: '#217346', // 엑셀 브랜드 그린fontSize: 12,headerFontSize: 12,rowHeight: 22,headerHeight: 24,borderRadius: 0,wrapperBorderRadius: 0,rowBorder: true,columnBorder: true,headerRowBorder: true,headerColumnBorder: true,})
<ExcelGrid<ProductRow> rowData={PRODUCT_ROWS} columnDefs={PRODUCT_COLUMN_DEFS} height="360px" />ModalGrid
Section titled “ModalGrid”DataGrid보다 폰트·행 높이를 살짝 줄인, 모달 안에 들어갈 크기의 그리드다. 페이지네이션 대신 상태값에
따른 행 자동 선택(autoSelectRow)과 행 단위 순차 처리 버튼(rowAction) 두 가지 전용 기능이 있다.
전용 테마 modalGridTheme은 excelGridTheme과 달리 agGridTheme.withParams()를 체이닝해서
브랜드 색상·폰트는 그대로 물려받고 폰트 크기·행/헤더 높이·여백만 살짝 줄인다.
import {agGridTheme} from './agGridTheme'
export const modalGridTheme = agGridTheme.withParams({ fontSize: 13, headerFontSize: 13, rowHeight: 32, headerHeight: 34, spacing: 6,})autoSelectRow — 특정 조건의 행을 자동 선택
Section titled “autoSelectRow — 특정 조건의 행을 자동 선택”gridApi가 준비된 이후,rowData/autoSelectRow가 바뀔 때마다 전체 행을 다시 평가해서 선택 상태를 최신화한다 — 예를 들어 수정 모달을 열었을 때 “현재 값에 해당하는 행”을 미리 선택된 채로 보여주는 용도다. 체크박스/하이라이트가 실제로 보이려면rowSelection도 함께 지정해야 한다.components/organisms/modal-grid/ModalGrid.tsx useEffect(() => {if (!gridApi || (!autoSelectRow && !rowAction)) {return}gridApi.forEachNode((node) => {if (node.data) {const shouldSelect =(autoSelectRow?.(node.data) ?? false) || (rowAction?.isCompleted(node.data) ?? false)node.setSelected(shouldSelect)}})}, [gridApi, rowData, autoSelectRow, rowAction])- 사용 예 — 특정 상태값(
autoSelectedProductId)이 바뀔 때마다 그 상품 행이 자동으로 선택된다.pages/sample/grid/Grid.tsx <ModalGrid<ProductRow>rowData={PRODUCT_ROWS}columnDefs={PRODUCT_COLUMN_DEFS}rowSelection={ROW_SELECTION}autoSelectRow={(row) => row.id === autoSelectedProductId}height="280px"/>
rowAction — 맨 위 행부터 한 행씩 순차 처리
Section titled “rowAction — 맨 위 행부터 한 행씩 순차 처리”rowData순서상 아직 처리되지 않은(isCompleted가 false인) 첫 번째 행의 버튼만 활성화되고, 나머지는 모두 비활성화된다.handleAction이 끝나 그 행이 완료 상태로 반영되면 다음 미완료 행의 버튼이 자동으로 활성화된다. 이미 처리 완료된 행은autoSelectRow와 마찬가지로 선택된 상태로 표시된다.components/organisms/modal-grid/ModalGrid.tsx export type ModalGridRowAction<T> = {label: stringisCompleted: (row: T) => booleanhandleAction: (row: T) => void | Promise<void>headerName?: stringwidth?: number}rowAction이 있으면 오른쪽 끝에 버튼 컬럼을 자동으로 덧붙인다(직접columnDefs에 추가할 필요 없음).pages/sample/grid/Grid.tsx <ModalGrid<ProductRow>rowData={ROW_ACTION_ROWS}columnDefs={PRODUCT_COLUMN_DEFS}rowSelection={ROW_SELECTION}rowAction={{label: '처리',isCompleted: (row) => processedProductIds.includes(row.id),handleAction: handleProcessProductRow,}}height="280px"/>- 버튼 클릭이
Promise를 반환하면 완료까지 버튼에 스피너가 뜬다. 처리 완료 후 다음 행 버튼이 실제로 활성화되게 하려면, 호출부가rowData(또는isCompleted가 참조하는 상태)를 갱신해야 한다 —ModalGrid는 완료 상태를 별도로 들고 있지 않고 매번rowData에서 다시 계산한다.
단순 사용 예 (autoSelectRow/rowAction 없이)
Section titled “단순 사용 예 (autoSelectRow/rowAction 없이)”<ModalGrid<ColumnNameTokenRow> rowData={rowData} columnDefs={COLUMN_DEFS} height="240px" />