Welcome to the 22nd installment of our AG-Grid React series! In this post, we're diving deep into one of the most powerful features for UI customization: Custom Cell Renderers.
AG-Grid is renowned for its flexibility, and while its default cell rendering is highly efficient and covers many use cases, real-world applications often demand unique visualizations and interactive elements within grid cells. This is where custom cell renderers come into play, allowing you to embed full-fledged React components directly into your grid cells, unlocking limitless possibilities for user experience.
Why Custom Cell Renderers?
Imagine a grid displaying product data. Instead of just text, you might want to:
- Show a product image thumbnail.
- Display a progress bar for stock levels.
- Render a "Buy Now" button or an "Edit" icon.
- Format numerical data with dynamic color coding based on its value.
- Include interactive elements like star ratings or dropdowns.
Custom cell renderers empower you to transform static data into rich, interactive, and visually appealing grid content using the full power of React.
Understanding the Basics: How AG-Grid Integrates React Components
At its core, AG-Grid allows you to specify a React component as the renderer for a particular column's cells. This component receives a special set of properties (props) from AG-Grid, providing all the necessary context about the cell, row, and grid state.
To implement a custom cell renderer, you'll typically follow these steps:
- Create a React component (functional or class-based).
- Define the component in your
colDef(column definition) using thecellRendererproperty. - Register the component with
AgGridReactusing theframeworkComponentsprop.
Step-by-Step Example: A Simple Status Indicator
Let's create a custom renderer to display a "Status" field with a specific color (e.g., green for 'Active', red for 'Inactive').
1. Create the React Component (`StatusRenderer.jsx`)
This functional component will receive a props.value containing the cell's data for the specified column (in this case, the status string).
import React from 'react';
const StatusRenderer = (props) => {
const status = props.value;
const statusColor = status === 'Active' ? 'green' : '#dc3545'; // Example: Bootstrap 'danger' red
return (
<span style={{ color: statusColor, fontWeight: 'bold' }}>
{status}
</span>
);
};
export default StatusRenderer;
2. Configure Your Column Definition (`colDef`)
In your colDef, you reference your custom component using the cellRenderer property. We'll register it by a string name for easy reference.
const columnDefs = useMemo(() => [
{ field: 'id', headerName: 'ID', flex: 0.5 },
{ field: 'name', headerName: 'Name', flex: 1 },
{
field: 'status',
headerName: 'Status',
flex: 0.7,
cellRenderer: 'statusRenderer', // Reference the registered component by its key
},
], []);
3. Register the Component with `AgGridReact`
For AG-Grid to recognize and use your custom React components, you need to register them using the frameworkComponents prop on the <AgGridReact /> component. The keys in this object will be used in your colDef.cellRenderer property.
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';
import StatusRenderer from './StatusRenderer'; // Import your custom renderer
const MyGridComponent = () => {
const gridRef = useRef();
const [rowData] = useState([
{ id: 1, name: 'Alice', status: 'Active' },
{ id: 2, name: 'Bob', status: 'Inactive' },
{ id: 3, name: 'Charlie', status: 'Active' },
]);
const columnDefs = useMemo(() => [
{ field: 'id', headerName: 'ID', flex: 0.5 },
{ field: 'name', headerName: 'Name', flex: 1 },
{
field: 'status',
headerName: 'Status',
flex: 0.7,
cellRenderer: 'statusRenderer', // Uses the key defined in frameworkComponents
},
], []);
// Register the custom renderer(s)
const frameworkComponents = useMemo(() => ({
statusRenderer: StatusRenderer, // Key: 'statusRenderer', Value: the imported component
}), []);
const defaultColDef = useMemo(() => ({
sortable: true,
filter: true,
resizable: true,
}), []);
return (
<div className="ag-theme-alpine" style={{ height: 400, width: '100%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
frameworkComponents={frameworkComponents} // Pass the registered components
></AgGridReact>
</div>
);
};
export default MyGridComponent;
Passing Custom Properties to Cell Renderers
The props object passed to your custom cell renderer contains standard AG-Grid properties (value, data, api, etc.). However, you might need to pass additional, custom information or callback functions from your parent grid component to the renderer. This is achieved using the cellRendererParams property within your colDef.
Example: An Action Button with a Custom Handler
Let's add a "View Details" button to each row that, when clicked, triggers a function defined in the parent grid component.
1. Create the React Component (`ActionRenderer.jsx`)
This component will receive the onViewDetails callback via props and will use props.data (the entire row's data) to pass relevant information back to the parent.
import React from 'react';
const ActionRenderer = (props) => {
const handleViewDetailsClick = () => {
// Call the custom function passed from cellRendererParams
// and pass the entire row data (props.data)
if (props.onViewDetails) {
props.onViewDetails(props.data);
}
};
return (
<button
onClick={handleViewDetailsClick}
style={{
backgroundColor: '#007bff', // Example: Bootstrap 'primary' blue
color: 'white',
border: 'none',
padding: '5px 10px',
borderRadius: '3px',
cursor: 'pointer',
}}
>
View {props.data.name}
</button>
);
};
export default ActionRenderer;
2. Update Your Column Definition and Grid Component
Now, in your main grid component, you'll define the onViewDetails function and pass it down via cellRendererParams.
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';
// Import your renderers
import StatusRenderer from './StatusRenderer';
import ActionRenderer from './ActionRenderer';
const MyGridComponent = () => {
const gridRef = useRef();
const [rowData] = useState([
{ id: 1, name: 'Alice', status: 'Active', age: 30 },
{ id: 2, name: 'Bob', status: 'Inactive', age: 24 },
{ id: 3, name: 'Charlie', status: 'Active', age: 35 },
]);
// Callback function to be passed to the renderer
const handleViewDetails = useCallback((rowData) => {
alert(`Viewing details for: ${rowData.name} (ID: ${rowData.id}, Age: ${rowData.age})`);
}, []); // Empty dependency array means this function is created once
const columnDefs = useMemo(() => [
{ field: 'id', headerName: 'ID', flex: 0.5 },
{ field: 'name', headerName: 'Name', flex: 1 },
{ field: 'age', headerName: 'Age', flex: 0.5 },
{
field: 'status',
headerName: 'Status',
flex: 0.7,
cellRenderer: 'statusRenderer',
},
{
headerName: 'Actions',
field: 'action', // This can be a dummy field or an existing unique field like 'id'
flex: 1,
cellRenderer: 'actionRenderer',
cellRendererParams: {
onViewDetails: handleViewDetails, // Pass the callback here
},
resizable: false, // Actions column typically not resizable
sortable: false, // Actions column typically not sortable
filter: false, // Actions column typically not filterable
},
], [handleViewDetails]); // Add handleViewDetails to dependencies, as it's used in colDef
// Register all custom renderers
const frameworkComponents = useMemo(() => ({
statusRenderer: StatusRenderer,
actionRenderer: ActionRenderer,
}), []); // Empty dependency array means this object is created once
const defaultColDef = useMemo(() => ({
sortable: true,
filter: true,
resizable: true,
}), []); // Empty dependency array means this object is created once
return (
<div className="ag-theme-alpine" style={{ height: 400, width: '100%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
frameworkComponents={frameworkComponents}
></AgGridReact>
</div>
);
};
export default MyGridComponent;
Advanced Considerations for Custom Cell Renderers
Performance Optimization with `React.memo`
While AG-Grid is highly optimized, complex custom cell renderers in large grids can sometimes impact performance. React provides React.memo to prevent functional components from re-rendering if their props haven't changed.
import React from 'react';
const StatusRenderer = (props) => {
// ... renderer logic ...
return (
<span style={{ color: statusColor, fontWeight: 'bold' }}>
{status}
</span>
);
};
// Wrap your component with React.memo to prevent unnecessary re-renders
export default React.memo(StatusRenderer);
Similarly, when passing functions as props (like onViewDetails in our example), using useCallback ensures the function reference doesn't change on every render of the parent component. This is crucial for React.memo to work effectively, as a new function reference would otherwise cause the child component to re-render.
Using React Hooks within Renderers
Your custom cell renderers are standard React components, meaning you can leverage all React Hooks (useState, useEffect, useContext, etc.) within them. This enables sophisticated state management and side effects directly within your cells.
For instance, you could have a cell renderer with an internal counter, a dropdown that manages its own selection, or a component that fetches additional data based on props.value or props.data.
Dynamic Renderer Selection with `cellRendererSelector`
What if you need different renderers for different rows within the same column? For example, a "Priority" column might show a text label for 'Low', but an urgent icon for 'High' or a progress bar for 'In Progress'. This is where cellRendererSelector comes in handy.
Instead of directly providing a component to cellRenderer, you provide a function to cellRendererSelector. This function receives the params object for the cell and should return an object defining which renderer to use and its parameters, based on the current cell's data.
const columnDefs = useMemo(() => [
// ... other column definitions
{
headerName: 'Priority',
field: 'priority',
// Function to dynamically select the cell renderer
cellRendererSelector: (params) => {
if (params.data.priority === 'High') {
return {
component: 'priorityHighRenderer', // A special renderer for High priority
params: { icon: '🚨', textColor: 'red' } // Custom params for this renderer
};
}
if (params.data.priority === 'Medium') {
return {
component: 'priorityDefaultRenderer', // Default renderer for other priorities
params: { textColor: 'orange' }
};
}
return {
component: 'priorityDefaultRenderer', // Fallback default
params: { textColor: 'green' }
};
},
flex: 1,
},
], []);
// You would then register both 'priorityHighRenderer' and 'priorityDefaultRenderer'
// in your frameworkComponents, similar to how StatusRenderer was registered.
This powerful feature allows for highly conditional and flexible cell rendering logic, adapting the UI based on the underlying data.
Conclusion
Custom cell renderers are a cornerstone of building highly interactive and visually rich data grids with AG-Grid and React. By understanding how to create, configure, and optimize these components, you gain unparalleled control over your grid's UI and user experience.
From simple status indicators to complex action buttons and dynamically chosen renderers, AG-Grid's integration with React makes it straightforward to embed any React component into your cells. Remember to consider performance optimizations like React.memo and useCallback for larger datasets.
Experiment with different types of renderers to enhance your AG-Grid applications. Happy coding!
```