reopt designreopt design
DocsExploreToolsPricingBuilder
시작하기
시작하기
Playground
핵심 개념
핵심 개념
DataGrid
DataGrid 개요
편집형 데이터 입력
운영 모니터링
대규모 데이터
원격 연동 계약
마이그레이션 플레이북
앱 조합 가이드
컬럼 설계 플레이북
구축·운영
Skills
릴리즈 노트
레퍼런스
Hook 레퍼런스
타입 레퍼런스
Oopt-datagrid
reopt designreopt design

AI 시대를 위한 디자인 시스템

  • 문서
  • 가격
  • 릴리즈 노트
  • GitHub
  • 서비스 약관
  • 개인정보처리방침

© 2026 reopt-ai. All rights reserved.

DataGrid
  1. 문서
  2. /
  3. DataGrid
  4. /
  5. 편집형 데이터 입력

DataGrid - 편집형 데이터 입력

폼 테이블 형태의 즉시 편집 UX를 빠르게 구성하는 패턴입니다.

reopt design · 업데이트 2026년 6월 26일

시작하기핵심 개념개요편집형 데이터 입력운영 모니터링대규모 데이터원격 연동 계약마이그레이션 플레이북앱 조합 가이드컬럼 설계 플레이북Skills릴리즈 노트Hook 레퍼런스타입 레퍼런스

1. 라이브 예제

스프레드시트 스타일로 즉시 편집 가능한 기본 데이터그리드. 셀 커밋 로그를 확인할 수 있습니다.

Last commit: -

Member 1
Owner
Seoul
Blocked
Member 2
Editor
Tokyo
Active
Member 3
Viewer
Singapore
Active
Member 4
Owner
Berlin
Active
Member 5
Editor
Seoul
Active
Member 6
Viewer
Tokyo
Blocked
Member 7
Owner
Singapore
Active
Member 8
Editor
Berlin
Active
Member 9
Viewer
Seoul
Active
Member 10
Owner
Tokyo
Active
Member 11
Editor
Singapore
Blocked
Member 12
Viewer
Berlin
Active
Member 13
Owner
Seoul
Active
Member 14
Editor
Tokyo
Active
Member 15
Viewer
Singapore
Active
Member 16
Owner
Berlin
Blocked
Member 17
Editor
Seoul
Active
Member 18
Viewer
Tokyo
Active
Member 19
Owner
Singapore
Active
Member 20
Editor
Berlin
Active

2. 설계 포인트

- 편집 가능한 컬럼만 `editable: true`로 제한합니다.

- `setValue`는 불변 업데이트를 반환해 상태 추적을 안정화합니다.

- 저장 전 검증이 필요하면 `onRowsChange` 단계에서 규칙을 적용합니다.

- 커스텀 편집기는 editor.kind = "custom"과 component 조합을 기본 경로로 사용합니다.

3. 커스텀 에디터 API

재사용 가능한 편집기는 column definition 안에서 kind: "custom"과 component를 함께 선언하는 방식이 기본입니다.

커스텀 편집기 로직은 component 안에서 `commit`, `cancel`, `setDraftValue`를 직접 호출하는 방식으로 마무리하는 편이 가장 예측 가능하고 재사용하기 쉽습니다.

tsx
import * as React from "react";
import {
  DataGrid,
  type DataGridColumn,
  type DataGridEditorContext,
} from "@reopt-ai/opt-datagrid";

interface MemberRow {
  id: number;
  status: "pending" | "active";
}

function StatusEditor(
  context: DataGridEditorContext<MemberRow, MemberRow["status"]>,
) {
  const { draftValue, setDraftValue, commit, cancel } = context;

  return (
    <div className="grid gap-2">
      <p className="text-xs text-zinc-500">Current: {draftValue}</p>
      <div className="flex gap-2">
        <button
          type="button"
          onClick={() => {
            setDraftValue("pending");
            commit();
          }}
        >
          Pending
        </button>
        <button
          type="button"
          onClick={() => {
            setDraftValue("active");
            commit();
          }}
        >
          Active
        </button>
      </div>
      <button type="button" onClick={cancel}>
        Cancel
      </button>
    </div>
  );
}

const columns: DataGridColumn<MemberRow, MemberRow["status"]>[] = [
  {
    id: "status",
    title: "Status",
    editable: true,
    editor: {
      kind: "custom",
      component: StatusEditor,
    },
    getValue: (row) => row.status,
    setValue: (row, nextValue) => ({ ...row, status: nextValue }),
  },
];

export function CustomEditorGrid() {
  const [rows, setRows] = React.useState<MemberRow[]>([
    { id: 1, status: "pending" },
    { id: 2, status: "active" },
  ]);

  return (
    <DataGrid
      rows={rows}
      columns={columns}
      onRowsChange={(nextRows) => setRows(Array.from(nextRows))}
      height={320}
      ariaLabel="Custom editor grid"
    />
  );
}

// custom editor는 component 기반으로만 선언합니다.

4. 인터셉트/검증 패턴

복수 셀 편집, 붙여넣기, 삭제는 `onCellsEdited`, `onPaste`, `onDelete` 에서 도메인 정책으로 제어할 수 있습니다.

tsx
import * as React from "react";
import {
  DataGrid,
  type DataGridCellEdit,
  type DataGridColumn,
  type DataGridSelection,
} from "@reopt-ai/opt-datagrid";

interface Row {
  id: number;
  name: string;
  role: string;
}

const initialRows: Row[] = [
  { id: 1, name: "Mina", role: "Owner" },
  { id: 2, name: "Joon", role: "Editor" },
];

const columns: DataGridColumn<Row>[] = [
  {
    id: "name",
    title: "Name",
    editable: true,
    getValue: (row) => row.name,
    setValue: (row, nextValue) => ({ ...row, name: nextValue }),
  },
  {
    id: "role",
    title: "Role",
    editable: true,
    getValue: (row) => row.role,
    setValue: (row, nextValue) => ({ ...row, role: nextValue }),
  },
];

function EditableWithPolicy() {
  const [rows, setRows] = React.useState(initialRows);

  const handleCellsEdited = React.useCallback(
    (edits: readonly DataGridCellEdit<Row>[]) => {
      const hasBlockedField = edits.some(
        (edit) => edit.column.id === "role" && edit.nextValue === "Owner",
      );
      if (hasBlockedField) return true; // 기본 적용 차단
      return undefined; // 기본 적용 허용
    },
    [],
  );

  const handleDelete = React.useCallback((selection: DataGridSelection) => {
    if (selection.start.row === 0) return true; // 첫 행 삭제 방지
    return undefined;
  }, []);

  return (
    <DataGrid
      rows={rows}
      columns={columns}
      onRowsChange={setRows}
      onCellsEdited={handleCellsEdited}
      onDelete={handleDelete}
      onPaste={() => undefined}
    />
  );
}

5. 코드 예제

tsx
import * as React from "react";
import { DataGrid, type DataGridColumn } from "@reopt-ai/opt-datagrid";

interface MemberRow {
  id: number;
  name: string;
  role: string;
  region: string;
}

const columns: DataGridColumn<MemberRow>[] = [
  {
    id: "name",
    title: "Name",
    width: 220,
    editable: true,
    getValue: (row) => row.name,
    setValue: (row, nextValue) => ({ ...row, name: nextValue }),
  },
  {
    id: "role",
    title: "Role",
    width: 160,
    editable: true,
    getValue: (row) => row.role,
    setValue: (row, nextValue) => ({ ...row, role: nextValue }),
  },
  {
    id: "region",
    title: "Region",
    width: 160,
    editable: true,
    getValue: (row) => row.region,
    setValue: (row, nextValue) => ({ ...row, region: nextValue }),
  },
];

export function EditableGridExample() {
  const [rows, setRows] = React.useState<MemberRow[]>([
    { id: 1, name: "Mina", role: "Owner", region: "Seoul" },
    { id: 2, name: "Joon", role: "Editor", region: "Tokyo" },
  ]);

  return (
    <DataGrid
      rows={rows}
      columns={columns}
      onRowsChange={(nextRows) => setRows(Array.from(nextRows))}
      height={420}
      ariaLabel="Editable member grid"
    />
  );
}
PreviousDataGrid 개요편집형, 운영 모니터링형, 대규모 데이터 시나리오로 나눈 DataGrid 실전 가이드DataGrid
DataGrid 개요 페이지로 이동
Next운영 모니터링실시간 운영 화면에 맞는 고정 헤더, 이벤트 로그, 선택 패턴DataGrid