Welcome to another installment in our AG-Grid React series! In this #23 post, we're diving into one of the most fundamental and sought-after features of any data grid: cell editing. Providing users with the ability to modify data directly within the grid significantly enhances interactivity and user experience.
AG-Grid offers a powerful and flexible cell editing mechanism, and integrating it with React is straightforward. This post will cover the basics, from enabling editing to handling updated values, setting you up for more advanced custom editors in future discussions.
Enabling Cell Editing in AG-Grid React
The first step to enabling cell editing is to configure your grid and columns appropriately. AG-Grid provides two primary ways to enable editing: globally for the entire grid, or on a per-column basis.
Global Cell Editing
While less common for production applications (as you usually want to control which cells are editable), you can enable editing for all cells in the grid by setting a property on your gridOptions (or directly on the AgGridReact component if you're not using a separate gridOptions object). However, it's generally recommended to define editability per column for finer control.
Per-Column Cell Editing
This is the most common and recommended approach. You enable editing for specific columns by setting the editable: true property in their respective columnDefs.
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css'; // Core grid CSS
import 'ag-grid-community/styles/ag-theme-alpine.css'; // Theme CSS
const CellEditingBasics = () => {
const gridRef = useRef();
const [rowData, setRowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000 },
{ make: 'Ford', model: 'Mondeo', price: 32000 },
{ make: 'Porsche', model: 'Boxster', price: 72000 }
]);
const [columnDefs, setColumnDefs] = useState([
{ field: 'make', editable: true }, // This column is editable
{ field: 'model', editable: false }, // This column is NOT editable
{ field: 'price', editable: true, type: 'numericColumn' } // This column is also editable
]);
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
// Other grid properties can go here
></AgGridReact>
</div>
);
};
export default CellEditingBasics;
In the example above, the 'make' and 'price' columns are set to be editable, while 'model' is not. When a column is editable, AG-Grid automatically provides a default text editor.
The Default Cell Editor
When you set editable: true for a column, AG-Grid uses its built-in text editor by default. This editor allows users to input text directly into the cell. While simple, it's sufficient for many use cases involving string or numeric input. For more complex input types (e.g., dropdowns, dates, custom validations), you'd typically implement custom cell editors, which we will explore in a future post.
Starting and Stopping Edits
Users can initiate cell editing in several ways:
- Double-click: The most common way, where a user double-clicks on an editable cell.
- Keyboard navigation: Navigating to an editable cell and pressing
Enter. - Programmatically: Via the grid API (
gridApi.startEditingCell()), though this is usually for advanced scenarios.
Editing stops when:
- The user presses
Enter(commits the change). - The user presses
Escape(cancels the change). - The user clicks outside the editing cell (blur event, commits the change by default).
- The user navigates to another cell using arrow keys (commits the change).
Handling Data Changes: The onCellValueChanged Event
Enabling editing is only half the battle. Once a user edits a cell, you need to capture that change and update your application's underlying data store (e.g., React state, Redux store, or a backend server). AG-Grid provides the onCellValueChanged event for precisely this purpose.
This event fires every time a cell's value is successfully updated by the user through the grid's editor. The event object passed to your handler contains crucial information, including:
rowIndex: The index of the row that was edited.column.colId: The ID of the column that was edited.data: The entire row data object after the edit.oldValue: The value of the cell before the edit.newValue: The new value of the cell after the edit.
Here's how you can implement an onCellValueChanged handler to update your React state:
import React, { useState, useRef, useMemo, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
const CellEditingWithUpdates = () => {
const gridRef = useRef();
const [rowData, setRowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000 },
{ make: 'Ford', model: 'Mondeo', price: 32000 },
{ make: 'Porsche', model: 'Boxster', price: 72000 }
]);
const [columnDefs, setColumnDefs] = useState([
{ field: 'make', editable: true },
{ field: 'model' }, // Not editable
{ field: 'price', editable: true, type: 'numericColumn' }
]);
// Handler for when a cell value changes
const onCellValueChanged = useCallback((event) => {
console.log('Cell Value Changed:', event.data);
// You can update your backend or global state here.
// For a simple React state update:
// Note: For large datasets, consider more optimized ways to update state
// or ensure your immutable updates are efficient.
const updatedRowData = [...rowData]; // Create a shallow copy
const rowIndex = updatedRowData.findIndex(row => row === event.node.data); // Find the original row
if (rowIndex > -1) {
updatedRowData[rowIndex] = { ...event.data }; // Update the row with the new data
setRowData(updatedRowData); // Update state
}
}, [rowData]); // Depend on rowData to ensure we have the latest state
const defaultColDef = useMemo(() => {
return {
flex: 1,
minWidth: 100,
resizable: true,
};
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onCellValueChanged={onCellValueChanged} // Attach the handler here
animateRows={true} // Optional: for smooth row updates
></AgGridReact>
</div>
);
};
export default CellEditingWithUpdates;
In the onCellValueChanged handler, we receive the updated row object via event.data. We then update our component's rowData state with this new information. It's crucial to perform an immutable update on your state (e.g., creating a new array and new row objects) for React to detect changes and for AG-Grid to work efficiently with data updates.
Key Cell Editing Options
AG-Grid provides several properties to fine-tune the editing experience:
singleClickEdit: If set totrue, editing starts on a single click instead of a double-click. Be cautious with this, as it can interfere with row selection if not managed carefully.suppressClickEdit: If set totrue, clicking a cell will not start editing. Editing can still be triggered programmatically or via keyboard.stopEditingWhenCellsLoseFocus: (Default:true) Whentrue, editing stops if the cell loses focus (e.g., by clicking elsewhere or tabbing out). Iffalse, editing only stops when pressing Enter, Escape, or navigating with arrow keys.enableCellTextSelection: (Default:false) Iftrue, allows text selection inside cells. This can sometimes interfere with double-click editing, so be mindful of its interaction.
You can set these options directly on the AgGridReact component:
// ... inside your AgGridReact component ...
<AgGridReact
// ...
singleClickEdit={true} // Enable editing on single click
stopEditingWhenCellsLoseFocus={true} // Default, but good to know
// ...
></AgGridReact>
Full React Component Example
Here’s a complete React component demonstrating basic cell editing with state updates:
import React, { useState, useRef, useMemo, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react'; // the AG Grid React Component
import 'ag-grid-community/styles/ag-grid.css'; // Core grid CSS
import 'ag-grid-community/styles/ag-theme-alpine.css'; // Theme
const AgGridReactCellEditingExample = () => {
const gridRef = useRef(); // Ref for accessing AG Grid API
const [rowData, setRowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000 },
{ make: 'Ford', model: 'Mondeo', price: 32000 },
{ make: 'Porsche', model: 'Boxster', price: 72000 },
{ make: 'BMW', model: 'M3', price: 60000 },
{ make: 'Audi', model: 'A4', price: 45000 }
]);
// Column Definitions: Defines the columns to be displayed.
const [columnDefs] = useState([
{ field: 'make', editable: true, headerName: 'Car Make' },
{ field: 'model', editable: false, headerName: 'Car Model' }, // Not editable
{ field: 'price', editable: true, type: 'numericColumn', headerName: 'Price ($)' }
]);
// Default Column Definition: Applied to all columns unless overridden.
const defaultColDef = useMemo(() => {
return {
flex: 1,
minWidth: 100,
resizable: true,
sortable: true,
filter: true,
floatingFilter: true, // Example: Enable floating filters for all columns
};
}, []);
// Callback for when a cell's value changes
const onCellValueChanged = useCallback((event) => {
console.log('onCellValueChanged event:', event);
const { rowIndex, data } = event;
// Create a new array to trigger React state update (immutability)
const newRowData = [...rowData];
newRowData[rowIndex] = { ...data }; // Update the specific row
setRowData(newRowData); // Set new row data
// In a real application, you might send `event.data` or specific changes
// to a backend API here to persist the changes.
console.log(`Updated Row ${rowIndex}:`, newRowData[rowIndex]);
}, [rowData]); // Dependency on rowData to ensure the latest state is used
// Optional: Log grid ready event
const onGridReady = useCallback((params) => {
console.log('AG-Grid is ready!', params);
}, []);
return (
<div style={{ width: '100%', height: 'calc(100vh - 50px)' }}> {/* Adjust height as needed */}
<h2>AG-Grid Cell Editing Basics</h2>
<p>Double-click on the 'Car Make' or 'Price ($)' cells to edit.</p>
<div className="ag-theme-alpine" style={{ height: '70%', width: '100%' }}>
<AgGridReact
ref={gridRef} // Ref to access AG Grid API
rowData={rowData} // Row Data for the grid
columnDefs={columnDefs} // Column Definitions for the grid
defaultColDef={defaultColDef} // Default Column Properties
onGridReady={onGridReady} // Callback when grid is ready
onCellValueChanged={onCellValueChanged} // Handle cell value changes
// Optional: Grid Options for editing behavior
singleClickEdit={false} // Double-click to edit (default)
stopEditingWhenCellsLoseFocus={true} // Stop editing when cell loses focus
animateRows={true} // Animate changes in row height and position
></AgGridReact>
</div>
</div>
);
};
export default AgGridReactCellEditingExample;
Conclusion
Cell editing is a cornerstone of interactive data grids, and AG-Grid makes it remarkably simple to implement in a React application. By understanding how to enable editing via editable: true and how to capture changes with onCellValueChanged, you have the foundational knowledge to create dynamic and editable tables. From here, you can explore more advanced topics like custom cell editors, validation, and programmatic editing to build even richer user experiences.