Unlocking Interactivity: Handling Events in AG-Grid-React
AG-Grid is a powerful data grid for React applications, offering unparalleled features for displaying and manipulating tabular data. While simply rendering data is a great start, the real power comes from making your grid interactive and responsive to user actions and internal grid state changes. This is where AG-Grid events come into play.
In this thirtieth installment of our AG-Grid-React series, we'll dive deep into understanding, implementing, and leveraging events to create dynamic and highly functional user experiences.
Why Are Events Important in AG-Grid?
Events are the cornerstone of any interactive application. In AG-Grid, they provide hooks into various actions and state changes within the grid. By handling these events, you can:
- React to user interactions: Such as clicking a row or cell, sorting a column, or resizing.
- Perform data operations: Like saving changes to a backend when cell values are edited.
- Update UI components: Displaying details of a selected row, or showing a status message.
- Integrate with other parts of your application: Triggering side effects or updating application-level state.
- Control grid behavior dynamically: For example, changing column definitions based on user actions.
How to Handle AG-Grid Events in React
AG-Grid-React provides a straightforward way to attach event listeners. The most common method is by passing event handler functions as props directly to the <AgGridReact /> component. The names of these props typically follow an on[EventName] convention (e.g., onRowClicked, onCellValueChanged).
Each event handler function receives an event object as its argument. This object contains crucial context-specific information related to the event that just occurred, such as the row data, column definition, current value, and more.
The Essential onGridReady Event
One of the most critical events you'll encounter is onGridReady. This event fires once the grid has fully initialized and is ready to be interacted with. It's the primary way to gain access to the grid's API instances (gridApi and columnApi), which are essential for programmatically controlling the grid.
import React, { useState, useRef, 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 MyGridComponent = () => {
const gridRef = useRef();
const [gridApi, setGridApi] = useState(null);
const [columnApi, setColumnApi] = useState(null);
const onGridReady = useCallback((params) => {
setGridApi(params.api);
setColumnApi(params.columnApi);
console.log('Grid is ready!', params.api, params.columnApi);
// You can now use params.api or gridApi for programmatic operations
// e.g., params.api.sizeColumnsToFit();
}, []);
// ... other columnDefs, rowData, and event handlers
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={[]} // your data
columnDefs={[]} // your column definitions
onGridReady={onGridReady}
></AgGridReact>
</div>
);
};
export default MyGridComponent;
Common AG-Grid Events and Their Usage
Let's explore some frequently used events and how to implement them.
1. onRowClicked
Fired when a row is clicked. Useful for displaying row details or triggering navigation.
const onRowClicked = useCallback((event) => {
console.log('Row clicked!', event.data); // event.data contains the row's data object
alert(`You clicked on row with ID: ${event.data.id} and Make: ${event.data.make}`);
}, []);
The event object for onRowClicked provides properties like data (the row's data item), rowIndex, node (the row node object), and event (the native DOM event).
2. onCellValueChanged
Fired when a cell's value has been changed, typically after an edit. Essential for persisting data changes.
const onCellValueChanged = useCallback((event) => {
console.log('Cell value changed!', event.data, event.colDef.field, event.oldValue, event.newValue);
alert(`Value of ${event.colDef.field} changed from '${event.oldValue}' to '${event.newValue}' in row ID: ${event.data.id}`);
// Here you would typically send the updated data to your backend
// myApiService.updateRow(event.data.id, event.colDef.field, event.newValue);
}, []);
The event object for onCellValueChanged includes data (the entire updated row object), newValue, oldValue, colDef (the column definition of the changed cell), and rowIndex.
3. onColumnResized
Fired after a column has been resized by the user. Useful if you need to react to layout changes.
const onColumnResized = useCallback((event) => {
console.log('Column resized!', event.column.getColId(), event.finished);
if (event.finished) {
console.log(`Column '${event.column.getColId()}' finished resizing.`);
// You might want to save column state here
}
}, []);
The event object for onColumnResized gives you access to the column object (the resized column), finished (boolean indicating if the resize is complete), and source.
4. onSortChanged
Fired when the sort state of columns has changed.
const onSortChanged = useCallback((event) => {
const sortModel = event.api.getSortModel();
console.log('Sort changed!', sortModel);
alert(`Grid sorted by: ${sortModel.map(s => `${s.colId} (${s.sort})`).join(', ')}`);
}, []);
The event object for onSortChanged provides access to the api and columnApi, allowing you to retrieve the current sort model.
Comprehensive Example: Handling Multiple Events
Let's put it all together into a single React component that demonstrates handling `onGridReady`, `onRowClicked`, and `onCellValueChanged` to update local state and display feedback.
import React, { useState, useRef, useCallback, useMemo } from 'react';
import { AgGridReact } from 'ag-grid-react';
// AG-Grid CSS
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
function AgGridEventDemo() {
const gridRef = useRef();
const [rowData, setRowData] = useState([
{ id: 1, make: 'Toyota', model: 'Celica', price: 35000, quantity: 10 },
{ id: 2, make: 'Ford', model: 'Mondeo', price: 32000, quantity: 15 },
{ id: 3, make: 'Porsche', model: 'Boxster', price: 72000, quantity: 5 }
]);
const [eventLog, setEventLog] = useState([]);
const [selectedRowInfo, setSelectedRowInfo] = useState(null);
const addLogEntry = useCallback((message) => {
setEventLog(prevLog => [`${new Date().toLocaleTimeString()}: ${message}`, ...prevLog].slice(0, 10)); // Keep last 10
}, []);
const columnDefs = useMemo(() => [
{ field: 'id', headerName: 'ID', width: 70, editable: false },
{ field: 'make', headerName: 'Make', editable: true },
{ field: 'model', headerName: 'Model', editable: true },
{ field: 'price', headerName: 'Price', editable: true, valueFormatter: p => `$${p.value.toLocaleString()}` },
{ field: 'quantity', headerName: 'Quantity', editable: true, type: 'numericColumn' }
], []);
const defaultColDef = useMemo(() => ({
flex: 1,
minWidth: 100,
resizable: true,
sortable: true,
filter: true,
}), []);
// Event Handlers
const onGridReady = useCallback((params) => {
addLogEntry('Grid is ready!');
// You can use params.api here if needed, or store it in state
// params.api.sizeColumnsToFit();
}, [addLogEntry]);
const onRowClicked = useCallback((event) => {
const message = `Row clicked: ID=${event.data.id}, Make=${event.data.make}`;
addLogEntry(message);
setSelectedRowInfo(`Selected: ${event.data.make} ${event.data.model} (Price: $${event.data.price})`);
}, [addLogEntry]);
const onCellValueChanged = useCallback((event) => {
const message = `Cell changed: ID=${event.data.id}, Field=${event.colDef.field}, Old='${event.oldValue}', New='${event.newValue}'`;
addLogEntry(message);
// In a real app, you'd update your backend here.
// For this demo, we'll update local state directly (AG-Grid already updates its internal model)
setRowData(prevRowData =>
prevRowData.map(row =>
row.id === event.data.id ? { ...row, [event.colDef.field]: event.newValue } : row
)
);
}, [addLogEntry]);
const onSortChanged = useCallback((event) => {
const sortModel = event.api.getSortModel();
const message = sortModel.length > 0
? `Sort changed: ${sortModel.map(s => `${s.colId} (${s.sort})`).join(', ')}`
: 'Sort cleared';
addLogEntry(message);
}, [addLogEntry]);
const onColumnResized = useCallback((event) => {
if (event.finished) {
const message = `Column resized: ${event.column.getColId()} (Width: ${event.column.getActualWidth()}px)`;
addLogEntry(message);
}
}, [addLogEntry]);
return (
<div style={{ width: '100%', height: 'calc(100vh - 50px)', display: 'flex', flexDirection: 'column', gap: '20px' }}>
<h2>AG-Grid Event Handling Demo</h2>
<div style={{ display: 'flex', gap: '20px', flexGrow: 1 }}>
<div className="ag-theme-alpine" style={{ height: '100%', width: '70%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
editType={'fullRow'} // Allows editing cells
onGridReady={onGridReady}
onRowClicked={onRowClicked}
onCellValueChanged={onCellValueChanged}
onSortChanged={onSortChanged}
onColumnResized={onColumnResized}
></AgGridReact>
</div>
<div style={{ width: '30%', backgroundColor: '#f9f9f9', padding: '15px', borderRadius: '8px', border: '1px solid #ddd' }}>
<h3>Event Log</h3>
{selectedRowInfo && <p><strong>{selectedRowInfo}</strong></p>}
<ul style={{ listStyleType: 'none', padding: 0, margin: 0, maxHeight: 'calc(100% - 100px)', overflowY: 'auto' }}>
{eventLog.map((entry, index) => (
<li key={index} style={{ marginBottom: '5px', fontSize: '0.9em', color: '#333' }}>
<code>{entry}</code>
</li>
))}
</ul>
</div>
</div>
</div>
);
}
export default AgGridEventDemo;
Best Practices for Event Handling
- Use
useCallback: For event handlers in React functional components, wrap them inuseCallbackto prevent unnecessary re-renders of the grid and maintain referential stability. Remember to include dependencies if your handler relies on props or state. - Debounce/Throttle for frequent events: For events like
onViewportChangedoronColumnResizedthat can fire very rapidly, consider debouncing or throttling your handler function to prevent performance issues. - Understand the Event Object: Always inspect the
eventobject passed to your handler. It contains all the context you need to react appropriately. The AG-Grid documentation provides detailed information on what each event object contains. - Centralize Logic: For complex event handling, consider centralizing your logic in custom hooks or separate utility functions to keep your component clean.
- Error Handling: Implement robust error handling within your event handlers, especially when interacting with external services (e.g., API calls on
onCellValueChanged).
Conclusion
Events are the lifeblood of interactive AG-Grid applications. By mastering how to listen and react to various grid events, you can unlock a vast array of possibilities, from simple user feedback to complex data synchronization with your backend. The consistent `on[EventName]` prop pattern and the rich event object provide a powerful and intuitive mechanism for building highly responsive and dynamic data grids.
Experiment with different events, explore their respective event objects, and integrate them thoughtfully into your React components to elevate your AG-Grid user experience.