AG-Grid React Series #27: Mastering Value Getters and Value Formatters
Unlocking powerful data manipulation and presentation in your AG-Grid React applications.
Introduction: Beyond Raw Data Display
In the world of data grids, simply displaying raw data is often not enough. You frequently need to transform data before it's displayed, or format it for better readability and user experience. AG-Grid, when used with React, provides two powerful mechanisms for achieving this: valueGetters and valueFormatters. While they both deal with how data appears in a cell, they serve fundamentally different purposes. Understanding their distinctions and proper use is key to building robust and user-friendly grids.
This article, part of our comprehensive AG-Grid React Series, will dive deep into both concepts, providing clear explanations, practical examples, and best practices to help you leverage them effectively in your React projects.
Understanding Value Getters: Manipulating the Underlying Data
A valueGetter in AG-Grid is a function that allows you to calculate or derive the actual value of a cell from the row's data. Crucially, this function determines the underlying value that AG-Grid uses for all its internal operations, including sorting, filtering, aggregation, and editing. It effectively changes what AG-Grid perceives as the cell's true value, not just its visual representation.
When to use Value Getters:
- Complex Data Paths: When your data is nested or requires accessing properties that aren't directly at the root of the row object (e.g.,
data.address.street). - Derived Values: When the cell's value needs to be calculated from other fields within the same row (e.g., combining first and last names, calculating total price from quantity and unit price, age from a birth date).
- Default Values/Fallbacks: Providing a default value if a specific field is missing or null.
- Enabling Sorting/Filtering on Calculated Fields: Since the
valueGetterdefines the actual value, AG-Grid can sort and filter based on this derived result.
Value Getter Example: Combining Names and Calculating Age
Let's say your data has separate firstName and lastName fields, and a dateOfBirth. You want to display a "Full Name" column that can also be sorted, and an "Age" column.
const columnDefs = [
{
headerName: 'ID',
field: 'id',
width: 80,
},
{
headerName: 'Full Name',
valueGetter: params => {
// params.data provides access to the full row data
if (params.data.firstName && params.data.lastName) {
return `${params.data.firstName} ${params.data.lastName}`;
}
return params.data.firstName || params.data.lastName || 'N/A';
},
// This column can now be sorted and filtered by the full name string
sortable: true,
filter: true,
},
{
headerName: 'Age',
valueGetter: params => {
if (params.data.dateOfBirth) {
const today = new Date();
const birthDate = new Date(params.data.dateOfBirth);
let age = today.getFullYear() - birthDate.getFullYear();
const m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age; // Return the numeric age
}
return null;
},
sortable: true,
filter: 'agNumberColumnFilter', // Filter based on the numeric age
},
{
headerName: 'Email',
field: 'email',
},
];
// Example Row Data
const rowData = [
{ id: 1, firstName: 'Alice', lastName: 'Smith', dateOfBirth: '1990-05-15', email: 'alice@example.com' },
{ id: 2, firstName: 'Bob', lastName: 'Johnson', dateOfBirth: '1985-11-20', email: 'bob@example.com' },
{ id: 3, firstName: 'Charlie', dateOfBirth: '2000-01-01', email: 'charlie@example.com' }, // Missing lastName
{ id: 4, firstName: 'Diana', lastName: 'Prince', email: 'diana@example.com' }, // Missing dateOfBirth
];
In this example, the "Full Name" and "Age" columns don't correspond directly to a single field. Instead, their values are dynamically generated by the valueGetter, which AG-Grid then uses for all its internal logic like sorting and filtering.
Introducing Value Formatters: Enhancing Display Without Changing Data
A valueFormatter, in contrast to a valueGetter, is purely for display purposes. It takes the cell's existing value (which might be an original field value or a value derived by a valueGetter) and transforms it into a more readable or presentable string for the UI. The crucial point here is that the underlying cell value remains unchanged for AG-Grid's internal operations like sorting, filtering, and editing.
When to use Value Formatters:
- Currency Formatting: Displaying numbers as monetary values (e.g.,
1234.50as$1,234.50). - Date Formatting: Presenting dates in a user-friendly locale-specific format (e.g.,
2023-10-26T10:00:00ZasOct 26, 2023or10/26/2023). - Percentage Formatting: Converting decimal values to percentages (e.g.,
0.75as75%). - Custom String Presentation: Adding prefixes/suffixes, truncating text, or applying any display-only transformation.
- Localizing Display: Presenting values according to different regional conventions.
Value Formatter Example: Currency and Date Display
Let's say you have a price field which is a raw number, and a saleDate field as an ISO string, but you want to display them as a currency and a formatted date respectively. Sorting and filtering should still operate on the raw numeric and date string values.
const columnDefs = [
{
headerName: 'Product',
field: 'productName',
},
{
headerName: 'Price',
field: 'price',
valueFormatter: params => {
// params.value is the actual value of the cell (e.g., 1234.50)
if (params.value === null || params.value === undefined || isNaN(params.value)) {
return '';
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(params.value);
},
// Sorting and filtering will still use the numeric `price` field
sortable: true,
filter: 'agNumberColumnFilter', // Use a number filter for raw price
},
{
headerName: 'Sale Date',
field: 'saleDate',
valueFormatter: params => {
if (!params.value) {
return '';
}
try {
return new Date(params.value).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
} catch (e) {
console.error('Invalid date for formatting:', params.value);
return params.value; // Fallback to raw value
}
},
sortable: true,
filter: 'agDateColumnFilter', // Filter based on the actual date string
},
];
// Example Row Data
const rowData = [
{ productName: 'Laptop', price: 1234.50, quantity: 1, saleDate: '2023-10-26T14:30:00Z' },
{ productName: 'Mouse', price: 25.99, quantity: 3, saleDate: '2023-10-20T10:00:00Z' },
{ productName: 'Keyboard', price: 79.00, quantity: 2, saleDate: '2023-09-15T09:15:00Z' },
{ productName: 'Monitor', price: 299.99, quantity: 0, saleDate: '2023-11-01T11:00:00Z' },
];
Here, the "Price" column's underlying value remains 1234.50, 25.99, etc., and "Sale Date" remains the ISO string, which ensures that sorting and filtering work correctly based on the original data types. However, the valueFormatter transforms these into user-friendly strings for display.
Value Getters vs. Value Formatters: A Clear Distinction
It's critical to understand the fundamental difference between these two powerful functions:
valueGetter:- Purpose: Defines the actual, underlying value of a cell that AG-Grid uses internally.
- Impact: Affects sorting, filtering, editing, aggregation, and any other internal grid logic that relies on the cell's value. The returned value type matters (e.g., number for numeric filters).
- Input: Receives `params.data` (the entire row object) and can access any property.
- Output: Returns the value that AG-Grid considers the cell's true, manipulable value.
valueFormatter:- Purpose: Transforms the cell's value purely for display in the UI.
- Impact: Does not affect sorting, filtering, or other internal grid logic. The original value (or the value returned by a
valueGetter) is used for these operations. - Input: Receives `params.value` (the value of the cell as determined by
fieldorvalueGetter). - Output: Returns a string that will be displayed in the cell. The returned type is always a string for display.
Think of it this way: A valueGetter changes the ingredients and recipe of a dish (affecting its core properties), while a valueFormatter changes how the dish is plated and presented to the diner (only affecting its visual appeal).
Combining Value Getters and Value Formatters
There are scenarios where you'll want to use both a valueGetter and a valueFormatter on the same column. This is often the case when you need to calculate a value from multiple fields, and then also format that calculated value for display.
Example: Total Price with Currency Formatting
Let's create a "Total Price" column that calculates quantity * price (using a valueGetter) and then displays it as currency (using a valueFormatter). Critically, sorting and filtering should work on the calculated numeric total.
const columnDefs = [
{
headerName: 'Product',
field: 'productName',
},
{
headerName: 'Unit Price',
field: 'price',
// Only format for display; underlying value remains numeric
valueFormatter: params => {
return params.value ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(params.value) : '';
},
filter: 'agNumberColumnFilter',
},
{
headerName: 'Quantity',
field: 'quantity',
filter: 'agNumberColumnFilter',
},
{
headerName: 'Total Price',
// 1. Calculate the total value using a valueGetter
valueGetter: params => {
const unitPrice = params.data.price || 0;
const quantity = params.data.quantity || 0;
return unitPrice * quantity; // This is the numeric value for sorting/filtering
},
// 2. Format the calculated total value for display using a valueFormatter
valueFormatter: params => {
if (params.value === null || params.value === undefined || isNaN(params.value)) {
return '';
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(params.value);
},
sortable: true,
filter: 'agNumberColumnFilter', // Filter on the calculated numeric total
},
];
// Example Row Data
const rowData = [
{ productName: 'Laptop', price: 1234.50, quantity: 1 },
{ productName: 'Mouse', price: 25.99, quantity: 3 },
{ productName: 'Keyboard', price: 79.00, quantity: 2 },
{ productName: 'Monitor', price: 299.99, quantity: 0 }, // Example with zero quantity
{ productName: 'Webcam', price: null, quantity: 1 }, // Example with missing price
];
In this powerful combination, the valueGetter ensures that the "Total Price" column holds the correct numeric value for sorting and filtering, while the valueFormatter ensures that this calculated number is presented to the user in a clear, formatted currency string.
Key Takeaways and Best Practices
- Choose Wisely: Always consider if you need to manipulate the underlying data (
valueGetter) or just its presentation (valueFormatter). This fundamental decision impacts sorting, filtering, data integrity, and potential editing. - Performance Considerations: Both functions run for every visible cell, potentially multiple times during scrolling or grid refreshes. For extremely complex or computationally intensive logic, consider pre-calculating values in your data source if possible, especially for
valueGettersthat affect many rows. - Null/Undefined Checks: Always include robust checks for
null,undefined, or invalid values (e.g.,isNaN) within your getters and formatters to prevent errors and ensure graceful handling of incomplete or malformed data. - Consistency of Type: Ensure that the output of your
valueGetteris of a type that AG-Grid can effectively sort and filter (e.g., numbers for numeric columns, Date objects or ISO strings for date columns, true/false for boolean filters). - Test Thoroughly: After implementing getters and formatters, verify that sorting, filtering, editing, and any other grid interactions work exactly as expected.
- Custom Cell Renderers: For highly complex or interactive cell displays, where formatting isn't enough, consider using custom Cell Renderers. These offer the ultimate control over cell content but come with increased complexity.
Mastering valueGetters and valueFormatters is a crucial step in building sophisticated and user-friendly AG-Grid applications with React. By correctly applying these tools, you can transform raw datasets into clear, actionable, and beautifully presented information, greatly enhancing the user experience. Stay tuned for more insights in our AG-Grid React Series!