Overview
React Data Grid supports inline cell editing with built-in editors or custom editor components. Editing is controlled through column configuration and row change handlers.
Quick Start
Enable editing with the editable property and onRowsChange callback:
import { useState } from 'react';
import { DataGrid, type Column } from 'react-data-grid';
interface Row {
id: number;
name: string;
email: string;
}
const columns: readonly Column<Row>[] = [
{ key: 'id', name: 'ID' },
{ key: 'name', name: 'Name', editable: true },
{ key: 'email', name: 'Email', editable: true }
];
function MyGrid() {
const [rows, setRows] = useState(initialRows);
return (
<DataGrid
columns={columns}
rows={rows}
onRowsChange={setRows}
/>
);
}
By default, double-click a cell to start editing. The grid uses a text input editor when no custom editor is provided.
Column Configuration
editable
boolean | ((row: TRow) => boolean)
default:false
Enable cell editing. Can be a boolean or a function for conditional editing.const columns: Column<Row>[] = [
{
key: 'name',
name: 'Name',
editable: true // Always editable
},
{
key: 'status',
name: 'Status',
editable: (row) => row.isActive // Conditionally editable
}
];
renderEditCell
(props: RenderEditCellProps<TRow, TSummaryRow>) => ReactNode
Custom editor component. When set, the column is automatically editable.
Built-in Text Editor
Use the provided text editor:
import { renderTextEditor, type Column } from 'react-data-grid';
const columns: readonly Column<Row>[] = [
{
key: 'title',
name: 'Title',
renderEditCell: renderTextEditor
}
];
Custom Editors
Create custom editor components:
Text Input Editor
import type { RenderEditCellProps } from 'react-data-grid';
function TextEditor({ row, column, onRowChange, onClose }: RenderEditCellProps<Row>) {
return (
<input
className="rdg-text-editor"
autoFocus
value={row[column.key as keyof Row] as string}
onChange={(event) =>
onRowChange({ ...row, [column.key]: event.target.value })
}
onBlur={() => onClose(true)}
/>
);
}
const columns: Column<Row>[] = [
{
key: 'name',
name: 'Name',
renderEditCell: TextEditor
}
];
Dropdown Editor
import type { RenderEditCellProps } from 'react-data-grid';
interface Row {
id: number;
status: string;
}
const statuses = ['pending', 'active', 'completed'];
function DropdownEditor({ row, column, onRowChange, onClose }: RenderEditCellProps<Row>) {
return (
<select
autoFocus
value={row.status}
onChange={(event) => {
onRowChange({ ...row, status: event.target.value }, true);
onClose(true);
}}
onBlur={() => onClose(true)}
>
{statuses.map((status) => (
<option key={status} value={status}>
{status}
</option>
))}
</select>
);
}
const columns: Column<Row>[] = [
{
key: 'status',
name: 'Status',
renderEditCell: DropdownEditor
}
];
Number Editor
import type { RenderEditCellProps } from 'react-data-grid';
function NumberEditor({ row, column, onRowChange, onClose }: RenderEditCellProps<Row>) {
return (
<input
type="number"
autoFocus
value={row[column.key as keyof Row] as number}
onChange={(event) =>
onRowChange({ ...row, [column.key]: Number(event.target.value) })
}
onBlur={() => onClose(true)}
/>
);
}
Editor Props
The renderEditCell function receives these props:
interface RenderEditCellProps<TRow, TSummaryRow = unknown> {
/** The column being edited */
column: CalculatedColumn<TRow, TSummaryRow>;
/** Current row data */
row: TRow;
/** Row index */
rowIdx: number;
/** Update row data */
onRowChange: (row: TRow, commitChanges?: boolean) => void;
/** Close the editor */
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
}
onRowChange
(row: TRow, commitChanges?: boolean) => void
Updates the row data. Pass true as the second argument to commit changes immediately.// Update without committing (onChange)
onRowChange({ ...row, name: 'New Name' });
// Update and commit (onBlur, Enter key)
onRowChange({ ...row, name: 'New Name' }, true);
onClose
(commitChanges?: boolean, shouldFocusCell?: boolean) => void
Closes the editor.
commitChanges: Whether to save or discard changes
shouldFocusCell: Whether to focus the cell after closing
// Save and close
onClose(true);
// Cancel and close
onClose(false);
Editor Options
Additional configuration for cell editing behavior.
editorOptions.displayCellContent
Render cell content alongside the editor. Useful for modal editors.const columns: Column<Row>[] = [
{
key: 'description',
name: 'Description',
renderEditCell: ModalEditor,
editorOptions: {
displayCellContent: true
}
}
];
editorOptions.commitOnOutsideClick
Automatically commit changes when clicking outside the cell.editorOptions: {
commitOnOutsideClick: false // Must press Enter to commit
}
editorOptions.closeOnExternalRowChange
Close the editor when row data changes externally.
Opening Editors
Double Click (Default)
Double-click a cell to open the editor.
Single Click
Open editor on single click using onCellClick:
import type { CellMouseArgs, CellMouseEvent } from 'react-data-grid';
function onCellClick(args: CellMouseArgs<Row>, event: CellMouseEvent) {
if (args.column.key === 'name') {
args.selectCell(true); // true = open editor immediately
}
}
<DataGrid
columns={columns}
rows={rows}
onCellClick={onCellClick}
/>
Keyboard
- Press
Enter to open the editor
- Start typing to open the editor and replace content
- Press
Escape to cancel editing
- Press
Tab to save and move to the next cell
Handling Changes
The onRowsChange callback provides the updated rows and metadata:
import type { RowsChangeData } from 'react-data-grid';
function handleRowsChange(newRows: Row[], data: RowsChangeData<Row>) {
console.log('Changed rows:', data.indexes);
console.log('Changed column:', data.column.key);
setRows(newRows);
}
<DataGrid
columns={columns}
rows={rows}
onRowsChange={handleRowsChange}
/>
Cell Copy & Paste
Enable copy and paste with callbacks:
onCellCopy
(args: CellCopyArgs<R, SR>, event: CellClipboardEvent) => void
Callback when a cell’s content is copied.
onCellPaste
(args: CellPasteArgs<R, SR>, event: CellClipboardEvent) => R
Callback when content is pasted. Return the updated row.function handlePaste(
args: CellPasteArgs<Row>,
event: React.ClipboardEvent
): Row {
const text = event.clipboardData.getData('text/plain');
return { ...args.row, [args.column.key]: text };
}
<DataGrid
columns={columns}
rows={rows}
onRowsChange={setRows}
onCellPaste={handlePaste}
/>
Fill Handle
Drag the cell’s fill handle to copy values to adjacent cells:
onFill
(event: FillEvent<R>) => R
Callback when the fill handle is used. Return the updated target row.function handleFill({ sourceRow, targetRow, columnKey }: FillEvent<Row>): Row {
return { ...targetRow, [columnKey]: sourceRow[columnKey] };
}
<DataGrid
columns={columns}
rows={rows}
onRowsChange={setRows}
onFill={handleFill}
/>
The fill handle appears in the bottom-right corner of the selected cell when onFill is provided.
Validation
Implement validation in your editor:
import { useState } from 'react';
import type { RenderEditCellProps } from 'react-data-grid';
function ValidatedEditor({ row, column, onRowChange, onClose }: RenderEditCellProps<Row>) {
const [value, setValue] = useState(row[column.key as keyof Row] as string);
const [error, setError] = useState('');
function validate(val: string) {
if (val.length < 3) {
setError('Must be at least 3 characters');
return false;
}
setError('');
return true;
}
function handleBlur() {
if (validate(value)) {
onRowChange({ ...row, [column.key]: value }, true);
onClose(true);
}
}
return (
<div>
<input
autoFocus
value={value}
onChange={(e) => {
setValue(e.target.value);
validate(e.target.value);
}}
onBlur={handleBlur}
/>
{error && <div className="error">{error}</div>}
</div>
);
}
Keyboard Navigation
Control editor behavior with keyboard events:
import type { CellKeyDownArgs, CellKeyboardEvent } from 'react-data-grid';
function onCellKeyDown(args: CellKeyDownArgs<Row>, event: CellKeyboardEvent) {
// Prevent editor from opening on Enter
if (args.mode === 'SELECT' && event.key === 'Enter') {
event.preventGridDefault();
}
// Custom behavior in edit mode
if (args.mode === 'EDIT' && event.key === 'Escape') {
args.onClose(false); // Discard changes
event.preventGridDefault();
}
}
<DataGrid
columns={columns}
rows={rows}
onCellKeyDown={onCellKeyDown}
/>
TypeScript Types
interface Column<TRow, TSummaryRow = unknown> {
/** Enable editing */
readonly editable?: Maybe<boolean | ((row: TRow) => boolean)>;
/** Custom editor component */
readonly renderEditCell?: Maybe<(props: RenderEditCellProps<TRow, TSummaryRow>) => ReactNode>;
/** Editor configuration */
readonly editorOptions?: Maybe<{
readonly displayCellContent?: Maybe<boolean>;
readonly commitOnOutsideClick?: Maybe<boolean>;
readonly closeOnExternalRowChange?: Maybe<boolean>;
}>;
}
interface RenderEditCellProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
onRowChange: (row: TRow, commitChanges?: boolean) => void;
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
}
interface FillEvent<TRow> {
columnKey: string;
sourceRow: TRow;
targetRow: TRow;
}
interface CellCopyArgs<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
}
interface CellPasteArgs<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
}