Empowering User Interaction with Custom Toolbars in AG-Grid React
AG-Grid is renowned for its powerful data grid capabilities, offering an extensive array of features right out of the box. However, in many real-world applications, you'll encounter scenarios where a standard context menu or header filter isn't sufficient for the specific actions your users need. This is where custom toolbars come into play – allowing you to integrate application-specific actions, branding, and workflows directly into your grid's interface, significantly enhancing user experience and productivity.
In this installment of our AG-Grid React series, we'll dive deep into creating custom toolbars and defining unique actions that seamlessly interact with your AG-Grid instance.
Beyond the Basics: When Default Features Aren't Enough
While AG-Grid provides excellent default mechanisms for filtering, sorting, and row operations, there are often common actions that users expect to see prominently displayed and easily accessible. Consider these use cases:
- Data Export: A dedicated "Export to CSV" or "Export to Excel" button.
- Row Management: Buttons for "Add New Row," "Delete Selected Rows," or "Duplicate Row."
- Filtering/Sorting Controls: A "Clear All Filters" button or "Reset Sorting" action.
- Custom Workflows: Buttons to trigger specific business logic, like "Approve Selected Items" or "Generate Report."
Placing these actions in a custom toolbar makes them highly discoverable and improves the overall fluidity of the user's interaction with the data.
Designing Your Custom Toolbar Component
The Core Idea
A custom toolbar in AG-Grid React is essentially a standard React component that you render alongside your <AgGridReact> component. The key to its power lies in its ability to access and manipulate the AG-Grid instance through the gridApi and columnApi objects.
Setting Up Your Grid Component (`App.js`)
To enable your custom toolbar to interact with the grid, your parent component (e.g., App.js) needs to capture the gridApi and columnApi when the grid is ready. These are exposed via the onGridReady callback prop.
Let's prepare our App.js:
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';
import CustomToolbar from './CustomToolbar'; // We'll create this next
const App = () => {
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] = useState([
{ field: 'make', filter: true, sortable: true },
{ field: 'model', filter: true, sortable: true },
{ field: 'price', filter: 'agNumberColumnFilter', sortable: true },
]);
const defaultColDef = {
flex: 1,
minWidth: 100,
resizable: true,
};
const onGridReady = useCallback((params) => {
gridRef.current.api = params.api;
gridRef.current.columnApi = params.columnApi;
}, []);
return (
<div style={{ width: '100%', height: '500px', display: 'flex', flexDirection: 'column' }}>
<CustomToolbar gridApi={gridRef.current.api} columnApi={gridRef.current.columnApi} />
<div className="ag-theme-alpine" style={{ flexGrow: 1 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
rowSelection={'multiple'} // Enable row selection for future actions
/>
</div>
</div>
);
};
export default App;
Notice how we pass gridRef.current.api and gridRef.current.columnApi as props to our <CustomToolbar /> component. These will be undefined until onGridReady is called, which is fine for our toolbar's initial render as it will check for their presence.
Building the Toolbar Component (`CustomToolbar.jsx`)
Now, let's create the CustomToolbar.jsx component. This component will receive the gridApi and columnApi as props and use them to implement various actions.
Step-by-Step Implementation with Code
1. Create Your Custom Toolbar Component
Let's define a simple toolbar with three common actions: Export to CSV, Add New Row, and Clear All Filters.
// CustomToolbar.jsx
import React from 'react';
const CustomToolbar = ({ gridApi, columnApi }) => {
const exportToCsv = () => {
if (gridApi) {
gridApi.exportDataAsCsv();
} else {
console.warn('Grid API not available for export.');
}
};
const addNewRow = () => {
if (gridApi) {
const newItem = { make: 'New Make', model: 'New Model', price: Math.floor(Math.random() * 100000) };
gridApi.applyTransaction({ add: [newItem] });
} else {
console.warn('Grid API not available to add row.');
}
};
const clearAllFilters = () => {
if (gridApi) {
gridApi.setFilterModel(null);
} else {
console.warn('Grid API not available to clear filters.');
}
};
return (
<div style={{ padding: '10px', backgroundColor: '#f0f2f5', borderBottom: '1px solid #ddd', marginBottom: '10px' }}>
<button
onClick={exportToCsv}
style={{ marginRight: '10px', padding: '8px 15px', cursor: 'pointer' }}
disabled={!gridApi}
>
Export CSV
</button>
<button
onClick={addNewRow}
style={{ marginRight: '10px', padding: '8px 15px', cursor: 'pointer' }}
disabled={!gridApi}
>
Add New Row
</button>
<button
onClick={clearAllFilters}
style={{ padding: '8px 15px', cursor: 'pointer' }}
disabled={!gridApi}
>
Clear All Filters
</button>
</div>
);
};
export default CustomToolbar;
In this component:
- We receive
gridApiandcolumnApias props. - Each button has an
onClickhandler that calls a function. - Inside these functions, we check if
gridApiexists before calling its methods likeexportDataAsCsv(),applyTransaction(), orsetFilterModel(null). This handles the initial render wheregridApimight not yet be set. - Buttons are disabled if
gridApiis not available, providing visual feedback.
2. Integrate the Toolbar into Your Application
The App.js we defined earlier already handles the integration by rendering CustomToolbar above the AgGridReact component and passing the necessary API props.
Recap of App.js:
// App.js
import React, { useState, useRef, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
// AG-Grid styles
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import CustomToolbar from './CustomToolbar'; // Our custom toolbar component
const App = () => {
// gridRef will hold the grid's API and column API
const gridRef = useRef();
// Sample row data
const [rowData, setRowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000 },
{ make: 'Ford', model: 'Mondeo', price: 32000 },
{ make: 'Porsche', model: 'Boxster', price: 72000 },
]);
// Column definitions
const [columnDefs] = useState([
{ field: 'make', filter: true, sortable: true },
{ field: 'model', filter: true, sortable: true },
{ field: 'price', filter: 'agNumberColumnFilter', sortable: true },
]);
// Default column properties
const defaultColDef = {
flex: 1,
minWidth: 100,
resizable: true,
};
// Callback when the grid is ready, used to store gridApi and columnApi
const onGridReady = useCallback((params) => {
gridRef.current.api = params.api;
gridRef.current.columnApi = params.columnApi;
}, []);
return (
<div style={{ width: '100%', height: '500px', display: 'flex', flexDirection: 'column' }}>
{/* Render the custom toolbar above the grid */}
<CustomToolbar gridApi={gridRef.current.api} columnApi={gridRef.current.columnApi} />
{/* The AG-Grid component */}
<div className="ag-theme-alpine" style={{ flexGrow: 1 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
rowSelection={'multiple'} // Enable selection for potential future actions
/>
</div>
</div>
);
};
export default App;
3. Basic Styles (Optional but good for presentation)
The inline styles in CustomToolbar.jsx provide a basic look. For a more robust solution, you'd typically use a CSS module or a dedicated stylesheet.
/* For example, in App.css or a module for CustomToolbar */
.custom-toolbar {
padding: 10px;
background-color: #f0f2f5;
border-bottom: 1px solid #ddd;
margin-bottom: 10px;
display: flex;
gap: 10px; /* Spacing between buttons */
}
.custom-toolbar button {
padding: 8px 15px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s ease;
}
.custom-toolbar button:hover:not(:disabled) {
background-color: #e0e0e0;
}
.custom-toolbar button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
You would then update your CustomToolbar.jsx to use a class name:
// ...
import './CustomToolbar.css'; // if using a separate CSS file
// ...
return (
<div className="custom-toolbar">
<button onClick={exportToCsv} disabled={!gridApi}>
Export CSV
</button>
<button onClick={addNewRow} disabled={!gridApi}>
Add New Row
</button>
<button onClick={clearAllFilters} disabled={!gridApi}>
Clear All Filters
</button>
</div>
);
// ...
Alternative Approach: Leveraging AG-Grid's Status Bar for Actions
While placing a custom component above the grid is common for toolbars, AG-Grid also provides a dedicated Status Bar feature that can house custom components. This is typically used for displaying aggregate information, but it can also serve as a fixed "toolbar" at the bottom of the grid, especially for actions related to selected rows or overall grid status.
To use the status bar, you define statusBar in your gridOptions and point to a custom React component using statusPanels and frameworkComponents:
// In App.js
import React, { useState, useRef, useCallback, useMemo } 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';
// For status bar component
const CustomStatusBarComponent = ({ api, columnApi }) => {
const onDeleteSelected = () => {
if (api) {
const selectedRows = api.getSelectedRows();
if (selectedRows.length > 0) {
api.applyTransaction({ remove: selectedRows });
console.log('Deleted selected rows:', selectedRows);
} else {
alert('No rows selected to delete!');
}
}
};
return (
<div style={{ padding: '5px', backgroundColor: '#e6e6e6', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
<button onClick={onDeleteSelected} disabled={!api} style={{ padding: '5px 10px', cursor: 'pointer' }}>
Delete Selected
</button>
</div>
);
};
const AppWithStatusBar = () => {
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] = useState([
{ field: 'make', filter: true, sortable: true, checkboxSelection: true },
{ field: 'model', filter: true, sortable: true },
{ field: 'price', filter: 'agNumberColumnFilter', sortable: true },
]);
const defaultColDef = { flex: 1, minWidth: 100, resizable: true };
const statusBar = useMemo(() => {
return {
statusPanels: [
{
statusPanel: 'agTotalAndFilteredRowCountComponent',
align: 'left',
},
{
statusPanel: 'agSelectedRowCountComponent',
align: 'center',
},
{
statusPanel: 'CustomStatusBarComponent', // Our custom component
align: 'right',
},
],
};
}, []);
const frameworkComponents = useMemo(() => {
return {
CustomStatusBarComponent: CustomStatusBarComponent,
};
}, []);
return (
<div style={{ width: '100%', height: '500px' }} className="ag-theme-alpine">
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
rowSelection={'multiple'}
statusBar={statusBar}
frameworkComponents={frameworkComponents}
/>
</div>
);
};
export default AppWithStatusBar;
This approach is excellent for actions that should be consistently available at the bottom of the grid, often tied to selected rows or summary information.
Enhancing Your Custom Toolbar
- Conditional Rendering: Show/hide buttons based on user permissions or grid state (e.g., disable "Delete Selected" if no rows are selected using
gridApi.getSelectedRows().length === 0). - Tooltips: Provide helpful hints on what each action does.
- Styling and Theming: Integrate your toolbar seamlessly with your application's design system using CSS.
- Dynamic Actions: Pass additional props to your toolbar component to make it more generic and reusable across different grid instances, or to dynamically adjust actions based on the data context.
- Loading States: Disable buttons and show loading spinners for long-running operations.
Conclusion
Custom toolbars are a powerful feature for extending AG-Grid's functionality and tailoring it precisely to your application's needs. By creating dedicated React components and leveraging the gridApi and columnApi, you can implement any action imaginable, from simple data exports to complex business logic. Whether you place your toolbar above the grid or integrate it into the status bar, the flexibility of AG-Grid React ensures your users have a highly intuitive and efficient experience.