Excel-like grid component built with React, with editors, keyboard navigation, copy & paste, and the like
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive selection system supporting single cells, multiple rows, and range selection with keyboard shortcuts and event handling.
Multi-row selection system with various selection methods and interaction patterns.
interface RowSelection {
/** Enable shift-click for multi-row selection */
enableShiftSelect?: boolean;
/** Called when rows are selected */
onRowsSelected?: (rows: SelectedRow[]) => void;
/** Called when rows are deselected */
onRowsDeselected?: (rows: SelectedRow[]) => void;
/** Show checkbox column for row selection */
showCheckbox?: boolean;
/** Method for determining which rows are selected */
selectBy: SelectionMethod;
}
type SelectionMethod =
| { indexes: number[] }
| { isSelectedKey: string }
| { keys: { values: any[]; rowKey: string } };
interface SelectedRow {
rowIdx: number;
row: any;
}Row Selection Examples:
import ReactDataGrid from 'react-data-grid';
// Selection by indexes
const IndexBasedSelection = () => {
const [selectedIndexes, setSelectedIndexes] = useState([]);
const rowSelection = {
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: (rows) => {
const newIndexes = [...selectedIndexes, ...rows.map(r => r.rowIdx)];
setSelectedIndexes(newIndexes);
},
onRowsDeselected: (rows) => {
const deselectedIndexes = rows.map(r => r.rowIdx);
setSelectedIndexes(selectedIndexes.filter(i => !deselectedIndexes.includes(i)));
},
selectBy: {
indexes: selectedIndexes
}
};
return (
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
rowSelection={rowSelection}
/>
);
};
// Selection by row property
const PropertyBasedSelection = () => {
const [rows, setRows] = useState(initialRows);
const rowSelection = {
showCheckbox: true,
onRowsSelected: (selectedRows) => {
const updatedRows = rows.map(row => {
const isSelected = selectedRows.some(sr => sr.row.id === row.id);
return { ...row, isSelected };
});
setRows(updatedRows);
},
onRowsDeselected: (deselectedRows) => {
const updatedRows = rows.map(row => {
const isDeselected = deselectedRows.some(dr => dr.row.id === row.id);
return isDeselected ? { ...row, isSelected: false } : row;
});
setRows(updatedRows);
},
selectBy: {
isSelectedKey: 'isSelected'
}
};
return (
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
rowSelection={rowSelection}
/>
);
};
// Selection by unique keys
const KeyBasedSelection = () => {
const [selectedKeys, setSelectedKeys] = useState([]);
const rowSelection = {
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: (rows) => {
const newKeys = [...selectedKeys, ...rows.map(r => r.row.id)];
setSelectedKeys([...new Set(newKeys)]);
},
onRowsDeselected: (rows) => {
const deselectedKeys = rows.map(r => r.row.id);
setSelectedKeys(selectedKeys.filter(k => !deselectedKeys.includes(k)));
},
selectBy: {
keys: {
values: selectedKeys,
rowKey: 'id'
}
}
};
return (
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
rowSelection={rowSelection}
/>
);
};Single cell and range selection for data manipulation and navigation.
interface CellSelection {
/** Enable cell selection functionality */
enableCellSelect?: boolean;
/** Called when a cell is selected */
onCellSelected?: (position: Position) => void;
/** Called when a cell is deselected */
onCellDeSelected?: (position: Position) => void;
/** Cell navigation mode */
cellNavigationMode?: 'none' | 'loopOverRow' | 'changeRow';
}
interface Position {
/** Column index */
idx: number;
/** Row index */
rowIdx: number;
}Cell Selection Example:
const CellSelectionGrid = () => {
const [selectedCell, setSelectedCell] = useState(null);
const handleCellSelected = (position) => {
setSelectedCell(position);
console.log('Cell selected:', position);
};
const handleCellDeSelected = (position) => {
setSelectedCell(null);
console.log('Cell deselected:', position);
};
return (
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
enableCellSelect={true}
cellNavigationMode="changeRow"
onCellSelected={handleCellSelected}
onCellDeSelected={handleCellDeSelected}
/>
);
};Multi-cell range selection for advanced data operations.
interface CellRangeSelection {
/** Called when range selection begins */
onStart?: (selectedRange: SelectionRange) => void;
/** Called during range selection updates */
onUpdate?: (selectedRange: SelectionRange) => void;
/** Called when range selection is completed */
onComplete?: (selectedRange: SelectionRange) => void;
}
interface SelectionRange {
/** Top-left corner of selection */
topLeft: Position;
/** Bottom-right corner of selection */
bottomRight: Position;
}Range Selection Example:
const RangeSelectionGrid = () => {
const [selectionRange, setSelectionRange] = useState(null);
const cellRangeSelection = {
onStart: (range) => {
console.log('Range selection started:', range);
setSelectionRange(range);
},
onUpdate: (range) => {
console.log('Range selection updated:', range);
setSelectionRange(range);
},
onComplete: (range) => {
console.log('Range selection completed:', range);
// Perform operations on selected range
const selectedData = extractRangeData(range);
console.log('Selected data:', selectedData);
}
};
const extractRangeData = (range) => {
const data = [];
for (let rowIdx = range.topLeft.rowIdx; rowIdx <= range.bottomRight.rowIdx; rowIdx++) {
const row = rows[rowIdx];
const rowData = {};
for (let colIdx = range.topLeft.idx; colIdx <= range.bottomRight.idx; colIdx++) {
const column = columns[colIdx];
rowData[column.key] = row[column.key];
}
data.push(rowData);
}
return data;
};
return (
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
enableCellSelect={true}
cellRangeSelection={cellRangeSelection}
/>
);
};Comprehensive event system for handling user interactions and grid events.
interface GridEvents {
// Row Events
/** Called when a row is clicked */
onRowClick?: (rowIdx: number, row: any, column: Column) => void;
/** Called when a row is double-clicked */
onRowDoubleClick?: (rowIdx: number, row: any, column: Column) => void;
/** Called when a row is selected */
onRowSelect?: (rowIdx: number, row: any) => void;
// Cell Events
/** Called when a cell is clicked */
onCellClick?: (position: Position, value: any, row: any) => void;
/** Called when a cell is double-clicked */
onCellDoubleClick?: (position: Position, value: any, row: any) => void;
/** Called when a cell context menu is triggered */
onCellContextMenu?: (position: Position, value: any, row: any) => void;
/** Called when a cell is expanded */
onCellExpand?: (args: CellExpandArgs) => void;
// Keyboard Events
/** Called on key down events */
onGridKeyDown?: (event: GridKeyboardEvent) => void;
/** Called on key up events */
onGridKeyUp?: (event: GridKeyboardEvent) => void;
// Data Events
/** Called when grid data is updated */
onGridRowsUpdated?: (event: GridRowsUpdatedEvent) => void;
/** Called when grid is sorted */
onGridSort?: (sortColumn: string, sortDirection: SortDirection) => void;
/** Called when grid is filtered */
onFilter?: (filter: Filter) => void;
// Layout Events
/** Called when grid is scrolled */
onScroll?: (scrollState: ScrollState) => void;
/** Called when a column is resized */
onColumnResize?: (idx: number, width: number) => void;
}
interface GridKeyboardEvent {
/** Row index */
rowIdx: number;
/** Column index */
idx: number;
/** Key that was pressed */
key: string;
/** Original keyboard event */
originalEvent: KeyboardEvent;
}
interface CellExpandArgs {
/** Row index */
rowIdx: number;
/** Column index */
idx: number;
/** Row data */
rowData: any;
/** Expand arguments */
expandArgs: any;
}Comprehensive Event Handling Example:
const EventHandlingGrid = () => {
const [eventLog, setEventLog] = useState([]);
const addEvent = (eventType, data) => {
const event = {
timestamp: new Date().toISOString(),
type: eventType,
data
};
setEventLog(prev => [event, ...prev.slice(0, 9)]); // Keep last 10 events
};
const handleRowClick = (rowIdx, row, column) => {
addEvent('Row Click', { rowIdx, row: row.name, column: column.name });
};
const handleRowDoubleClick = (rowIdx, row, column) => {
addEvent('Row Double Click', { rowIdx, row: row.name });
};
const handleCellClick = (position, value, row) => {
addEvent('Cell Click', { position, value, row: row.name });
};
const handleKeyDown = (event) => {
addEvent('Key Down', {
key: event.key,
position: { rowIdx: event.rowIdx, idx: event.idx }
});
};
const handleGridSort = (sortColumn, sortDirection) => {
addEvent('Sort', { sortColumn, sortDirection });
};
const handleScroll = (scrollState) => {
addEvent('Scroll', {
scrollTop: scrollState.scrollTop,
scrollLeft: scrollState.scrollLeft
});
};
return (
<div>
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
enableCellSelect={true}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
onCellClick={handleCellClick}
onGridKeyDown={handleKeyDown}
onGridSort={handleGridSort}
onScroll={handleScroll}
/>
{/* Event Log Display */}
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f8f9fa' }}>
<h4>Event Log:</h4>
{eventLog.map((event, index) => (
<div key={index} style={{ fontSize: '12px', marginBottom: '4px' }}>
<strong>{event.type}</strong> - {event.timestamp} -
{JSON.stringify(event.data)}
</div>
))}
</div>
</div>
);
};Built-in keyboard shortcuts for navigation and interaction.
interface KeyboardNavigation {
/** Cell navigation behavior */
cellNavigationMode?: 'none' | 'loopOverRow' | 'changeRow';
/** Enable automatic cell focus */
enableCellAutoFocus?: boolean;
}Keyboard Shortcuts:
Custom Keyboard Handling:
const CustomKeyboardGrid = () => {
const handleKeyDown = (event) => {
const { key, rowIdx, idx } = event;
switch (key) {
case 'F2':
// Custom: Start editing with F2
console.log('Start editing cell:', { rowIdx, idx });
event.originalEvent.preventDefault();
break;
case 'Delete':
// Custom: Clear cell content
console.log('Clear cell:', { rowIdx, idx });
// Implement clear logic
break;
case 'Insert':
// Custom: Insert new row
console.log('Insert new row at:', rowIdx);
// Implement insert logic
break;
default:
// Let grid handle other keys
break;
}
};
return (
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
enableCellSelect={true}
cellNavigationMode="changeRow"
onGridKeyDown={handleKeyDown}
/>
);
};Managing selection state and integrating with application state.
const SelectionStateGrid = () => {
const [rows, setRows] = useState(initialRows);
const [selectedRowIds, setSelectedRowIds] = useState([]);
const [selectedCell, setSelectedCell] = useState(null);
// Row selection handlers
const handleRowsSelected = (selectedRows) => {
const newIds = selectedRows.map(r => r.row.id);
setSelectedRowIds(prev => [...new Set([...prev, ...newIds])]);
};
const handleRowsDeselected = (deselectedRows) => {
const deselectedIds = deselectedRows.map(r => r.row.id);
setSelectedRowIds(prev => prev.filter(id => !deselectedIds.includes(id)));
};
// Cell selection handlers
const handleCellSelected = (position) => {
setSelectedCell(position);
};
// Programmatic selection methods
const selectAllRows = () => {
const allIds = rows.map(row => row.id);
setSelectedRowIds(allIds);
};
const clearSelection = () => {
setSelectedRowIds([]);
setSelectedCell(null);
};
const selectRowById = (id) => {
if (!selectedRowIds.includes(id)) {
setSelectedRowIds(prev => [...prev, id]);
}
};
return (
<div>
{/* Selection Controls */}
<div style={{ marginBottom: '10px' }}>
<button onClick={selectAllRows}>Select All</button>
<button onClick={clearSelection}>Clear Selection</button>
<span style={{ marginLeft: '20px' }}>
Selected: {selectedRowIds.length} rows
</span>
</div>
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={rows.length}
minHeight={400}
enableCellSelect={true}
rowSelection={{
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: handleRowsSelected,
onRowsDeselected: handleRowsDeselected,
selectBy: {
keys: {
values: selectedRowIds,
rowKey: 'id'
}
}
}}
onCellSelected={handleCellSelected}
/>
</div>
);
};Install with Tessl CLI
npx tessl i tessl/npm-react-data-grid@6.1.1