Skip to content

그리드 사용 예제

dictionary-app은 ag-grid를 용도별로 세 가지 공용 컴포넌트로 감싸서 쓴다 — 서버사이드 페이지네이션이 있는 DataGrid, 엑셀 시트처럼 보이는 ExcelGrid, 모달 안에 들어가는 ModalGrid. 모듈 등록/기본 테마는 DataGrid가 쓰는 agGridTheme.ts가 앱 시작 시 한 번만 수행하고, 나머지 둘은 그 위에 테마만 겹쳐 쓴다.

domain 페이지를 예시로, 서버사이드 페이지네이션 컨트롤까지 한 번에 연결하는 방법을 정리한다.

  1. 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",
    })
  1. rowData/columnDefs는 ag-grid 그대로 받고, 서버사이드 페이지네이션 정보(pagination)와 페이지 변경 콜백(handlePageChange)을 추가로 받아 하단 페이지네이션 컨트롤까지 함께 그린다.
    components/organisms/data-grid/DataGrid.tsx
    6 collapsed lines
    import {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>
    )
    }
  2. AgGridReactProps의 pagination(boolean, AG Grid 자체 클라이언트 페이지네이션)은 이 컴포넌트가 쓰는 서버사이드 pagination(객체)과 이름이 겹치고 용도도 달라 Omit으로 막아뒀다 — AG Grid 자체 페이지네이션은 쓰지 않고 항상 rowData로 넘어온 현재 페이지 데이터만 그린다.
  3. defaultColDef={{sortable: false}}로 모든 컬럼의 정렬을 기본으로 꺼둔다 — rowData가 서버 페이지네이션 결과(현재 페이지 몇십 건)뿐이라, 클라이언트 정렬을 켜두면 “그 페이지 안에서만” 재정렬될 뿐인데 전체 목록 기준 정렬처럼 보이는 문제가 있다.
  4. 페이지가 바뀔 때마다 refreshCells({force: true})로 모든 셀을 강제로 다시 계산한다 — No.처럼 행 데이터 자체가 아니라 pagination(외부 상태)으로 계산되는 값은 ag-grid의 기본 변경 감지에 걸리지 않기 때문이다.

사용 예: 서버사이드 페이지네이션 그리드

Section titled “사용 예: 서버사이드 페이지네이션 그리드”
  1. 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],
    )
  2. 조회 훅이 반환하는 {domains, pagination, handlePageChange}를 DataGrid에 그대로 연결한다(조회 훅 구현은 백엔드 API 사용 예제의 queries 항목 참고).
    pages/word/domain/Domain.tsx
    6 collapsed lines
    import 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}
    />
    )
    }
  3. 행 선택이 필요하면 rowSelection(ag-grid 옵션)을 그대로 전달한다 — DataGrid가 감싸지 않은 ag-grid 표준 prop은 ...agGridProps로 그대로 통과된다.

DataGrid와 달리 서버사이드 페이지네이션 컨트롤이 없다 — 엑셀 시트는 페이지가 아니라 스크롤로 데이터를 넘기는 게 자연스러워서, rowData를 그대로 넘기고 그리드 자체 스크롤로 나머지를 보게 한다.

  1. agGridTheme 대신 excelGridTheme을 적용한다 — 모듈 등록은 agGridTheme.ts가 이미 했으므로 여기서 다시 하지 않는다.
    components/organisms/excel-grid/ExcelGrid.tsx
    5 collapsed lines
    import {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>
    )
    }
  2. 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,
    })
pages/sample/grid/Grid.tsx
<ExcelGrid<ProductRow> rowData={PRODUCT_ROWS} columnDefs={PRODUCT_COLUMN_DEFS} height="360px" />

DataGrid보다 폰트·행 높이를 살짝 줄인, 모달 안에 들어갈 크기의 그리드다. 페이지네이션 대신 상태값에 따른 행 자동 선택(autoSelectRow)과 행 단위 순차 처리 버튼(rowAction) 두 가지 전용 기능이 있다. 전용 테마 modalGridTheme은 excelGridTheme과 달리 agGridTheme.withParams()를 체이닝해서 브랜드 색상·폰트는 그대로 물려받고 폰트 크기·행/헤더 높이·여백만 살짝 줄인다.

utils/modalGridTheme.ts
import {agGridTheme} from './agGridTheme'
export const modalGridTheme = agGridTheme.withParams({
fontSize: 13,
headerFontSize: 13,
rowHeight: 32,
headerHeight: 34,
spacing: 6,
})

autoSelectRow — 특정 조건의 행을 자동 선택

Section titled “autoSelectRow — 특정 조건의 행을 자동 선택”
  1. 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])
  2. 사용 예 — 특정 상태값(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 — 맨 위 행부터 한 행씩 순차 처리”
  1. rowData 순서상 아직 처리되지 않은(isCompleted가 false인) 첫 번째 행의 버튼만 활성화되고, 나머지는 모두 비활성화된다. handleAction이 끝나 그 행이 완료 상태로 반영되면 다음 미완료 행의 버튼이 자동으로 활성화된다. 이미 처리 완료된 행은 autoSelectRow와 마찬가지로 선택된 상태로 표시된다.
    components/organisms/modal-grid/ModalGrid.tsx
    export type ModalGridRowAction<T> = {
    label: string
    isCompleted: (row: T) => boolean
    handleAction: (row: T) => void | Promise<void>
    headerName?: string
    width?: number
    }
  2. 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"
    />
  3. 버튼 클릭이 Promise를 반환하면 완료까지 버튼에 스피너가 뜬다. 처리 완료 후 다음 행 버튼이 실제로 활성화되게 하려면, 호출부가 rowData(또는 isCompleted가 참조하는 상태)를 갱신해야 한다 — ModalGrid 는 완료 상태를 별도로 들고 있지 않고 매번 rowData에서 다시 계산한다.

단순 사용 예 (autoSelectRow/rowAction 없이)

Section titled “단순 사용 예 (autoSelectRow/rowAction 없이)”
pages/word/column/components/ColumnNameTokenGridStep.tsx
<ModalGrid<ColumnNameTokenRow> rowData={rowData} columnDefs={COLUMN_DEFS} height="240px" />