E-Commerce Product Table with AG Grid and React
Building an effective product display is at the heart of any successful e-commerce platform. Users need to browse, compare, and interact with products seamlessly. While a simple list might suffice for some, a dynamic and feature-rich table offers unparalleled power for displaying vast product catalogs, especially when dealing with advanced features like filtering, sorting, pagination, and custom actions. This is where AG Grid, coupled with React, truly shines.
In this installment of our AG-Grid-React series, we'll dive into constructing a robust e-commerce product table. We'll cover everything from displaying basic product details to integrating images and interactive "Add to Cart" buttons, leveraging AG Grid's powerful features to create a superior user experience.
Getting Started: Setting Up Your Environment
Before we build our product table, ensure you have a basic React project set up. If you're starting fresh, you can use Create React App or Vite. Once your React project is ready, install AG Grid's community package and its React wrapper:
npm install ag-grid-community ag-grid-react
# or
yarn add ag-grid-community ag-grid-react
The Core: The AgGridReact Component
The heart of our table will be the AgGridReact component. Let's set up a basic functional component to house our grid.
import React, { useState, useMemo } 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 ProductTable = () => {
// We'll define rowData and columnDefs here shortly
const defaultColDef = useMemo(() => {
return {
flex: 1,
minWidth: 100,
sortable: true,
filter: true,
resizable: true,
};
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 700, width: '100%' }}>
<AgGridReact
// rowData={rowData} // To be defined
// columnDefs={columnDefs} // To be defined
defaultColDef={defaultColDef}
pagination={true}
paginationPageSize={10}
></AgGridReact>
</div>
);
};
export default ProductTable;
In the code above, we've imported the necessary AG Grid components and styles. We've also added a defaultColDef which will apply common properties like sorting, filtering, and resizing to all columns unless explicitly overridden. We've also enabled basic pagination.
Defining Your Product Columns (columnDefs)
The columnDefs property is an array of objects, where each object defines a column in your grid. For an e-commerce product table, we'll want columns for product name, price, category, stock, and potentially a description.
// ... inside ProductTable component
const [columnDefs] = useState([
{ field: 'id', headerName: 'Product ID', maxWidth: 100 },
{ field: 'name', headerName: 'Product Name', minWidth: 200 },
{ field: 'category', headerName: 'Category', enableRowGroup: true },
{ field: 'price', headerName: 'Price', valueFormatter: p => `$${p.value.toFixed(2)}`, type: 'numericColumn' },
{ field: 'stock', headerName: 'In Stock', type: 'numericColumn' },
{ field: 'description', headerName: 'Description', flex: 2, minWidth: 300, wrapText: true, autoHeight: true },
]);
Here, we've defined several common product attributes. Notice the valueFormatter for the price to display it as currency, and wrapText / autoHeight for the description to handle longer texts gracefully.
Populating Data (rowData)
The rowData property is where you provide the actual product data to your grid. This data should be an array of objects, where each object corresponds to a row, and its properties match the field names in your columnDefs.
// ... inside ProductTable component
const [rowData] = useState([
{ id: 'P001', name: 'Wireless Bluetooth Headphones', category: 'Electronics', price: 79.99, stock: 150, description: 'High-quality audio with active noise cancellation and comfortable earcups.' },
{ id: 'P002', name: 'Organic Green Tea (20 Bags)', category: 'Groceries', price: 9.99, stock: 500, description: 'Premium organic green tea, individually wrapped for freshness.' },
{ id: 'P003', name: 'Smartphone Tripod Stand', category: 'Accessories', price: 24.50, stock: 230, description: 'Adjustable tripod for smartphones, perfect for vlogging and photography.' },
{ id: 'P004', name: 'Ergonomic Office Chair', category: 'Furniture', price: 249.00, stock: 80, description: 'Designed for comfort and support during long working hours. Adjustable height and recline.' },
{ id: 'P005', name: 'Waterproof Fitness Tracker', category: 'Electronics', price: 49.95, stock: 300, description: 'Monitor heart rate, steps, and sleep with this sleek, waterproof fitness tracker.' },
{ id: 'P006', name: 'Stainless Steel Water Bottle', category: 'Kitchenware', price: 18.00, stock: 450, description: 'Durable, insulated water bottle keeps drinks cold for 24 hours and hot for 12 hours.' },
// ... more product data
]);
Bringing Products to Life: Custom Cell Renderers
For e-commerce, static text isn't always enough. We often need to display images, interactive buttons, or custom formatted content within a cell. AG Grid's Cell Renderers are perfect for this.
1. Displaying Product Images
Let's create a cell renderer to display product images.
// src/components/ImageCellRenderer.js
import React from 'react';
const ImageCellRenderer = (props) => {
// props.value will be the image URL
return (
<img
src={props.value || 'https://via.placeholder.com/50'} // Fallback placeholder image
alt="Product"
style={{ width: '50px', height: '50px', objectFit: 'contain', margin: '5px' }}
/>
);
};
export default ImageCellRenderer;
Now, integrate this into our columnDefs and add an imageUrl field to our rowData.
// ... inside ProductTable component
import ImageCellRenderer from './ImageCellRenderer'; // Import the new component
const [columnDefs] = useState([
// ... existing column definitions
{
field: 'imageUrl',
headerName: 'Image',
cellRenderer: ImageCellRenderer,
maxWidth: 100,
sortable: false,
filter: false,
resizable: false,
suppressMovable: true, // Prevents moving this column
},
// ... rest of column definitions
]);
const [rowData] = useState([
{ id: 'P001', name: 'Wireless Bluetooth Headphones', category: 'Electronics', price: 79.99, stock: 150, description: 'High-quality audio with active noise cancellation and comfortable earcups.', imageUrl: 'https://cdn.shopify.com/s/files/1/0070/7032/files/headphone-product.jpg?v=1602434582' },
{ id: 'P002', name: 'Organic Green Tea (20 Bags)', category: 'Groceries', price: 9.99, stock: 500, description: 'Premium organic green tea, individually wrapped for freshness.', imageUrl: 'https://cdn.shopify.com/s/files/1/0533/2089/files/tea-product.jpg?v=1602434582' },
// ... add imageUrl to other products
]);
2. Action Buttons (View/Add to Cart)
Action buttons are crucial for interactivity. Let's create another cell renderer for "Add to Cart" and "View Details" buttons.
// src/components/ActionCellRenderer.js
import React from 'react';
const ActionCellRenderer = (props) => {
const handleAddToCart = () => {
alert(`Adding ${props.data.name} to cart!`);
// In a real app, dispatch an action to a cart state management system
};
const handleViewDetails = () => {
alert(`Viewing details for ${props.data.name} (ID: ${props.data.id})`);
// In a real app, navigate to a product detail page
};
return (
<div>
<button onClick={handleViewDetails} style={{ marginRight: '5px' }}>View</button>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
};
export default ActionCellRenderer;
And integrate this into our columnDefs:
// ... inside ProductTable component
import ActionCellRenderer from './ActionCellRenderer'; // Import the new component
const [columnDefs] = useState([
// ... existing column definitions
{
field: 'actions',
headerName: 'Actions',
cellRenderer: ActionCellRenderer,
minWidth: 180,
sortable: false,
filter: false,
resizable: false,
suppressMovable: true,
},
]);
Note that for ActionCellRenderer, we don't need a corresponding field in rowData. The renderer receives the entire row's data via props.data.
Enhancing User Experience: Filtering, Sorting, and Pagination
AG Grid provides powerful out-of-the-box features for data manipulation.
-
Sorting: Enabled by setting
sortable: trueindefaultColDefor specific column definitions. Users can click on column headers to sort. -
Filtering: Enabled by setting
filter: true. This adds a filter icon to the column header, allowing users to apply various filters (text, number, date). -
Pagination: As seen in our initial setup,
pagination={true}andpaginationPageSize={10}enable client-side pagination, breaking down large datasets into manageable pages.
For instance, to allow category-based filtering and price range filtering:
// ... inside columnDefs
{ field: 'category', headerName: 'Category', enableRowGroup: true, filter: 'agTextColumnFilter' },
{ field: 'price', headerName: 'Price', valueFormatter: p => `$${p.value.toFixed(2)}`, type: 'numericColumn', filter: 'agNumberColumnFilter' },
These filters provide dropdowns with various comparison operators (e.g., 'contains', 'greater than', 'in range').
Putting It All Together (ProductTable.js)
Here's a more complete look at our ProductTable.js component with all the pieces integrated:
import React, { useState, useMemo } 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 (or 'ag-theme-balham', etc.)
import ImageCellRenderer from './ImageCellRenderer';
import ActionCellRenderer from './ActionCellRenderer';
const ProductTable = () => {
const [columnDefs] = useState([
{ field: 'id', headerName: 'ID', maxWidth: 80, suppressMovable: true },
{
field: 'imageUrl',
headerName: 'Image',
cellRenderer: ImageCellRenderer,
maxWidth: 100,
sortable: false,
filter: false,
resizable: false,
suppressMovable: true,
},
{ field: 'name', headerName: 'Product Name', minWidth: 200, filter: 'agTextColumnFilter' },
{ field: 'category', headerName: 'Category', enableRowGroup: true, filter: 'agTextColumnFilter' },
{
field: 'price',
headerName: 'Price',
valueFormatter: p => `$${p.value.toFixed(2)}`,
type: 'numericColumn',
filter: 'agNumberColumnFilter',
},
{ field: 'stock', headerName: 'In Stock', type: 'numericColumn', filter: 'agNumberColumnFilter' },
{ field: 'description', headerName: 'Description', flex: 2, minWidth: 250, wrapText: true, autoHeight: true, filter: 'agTextColumnFilter' },
{
field: 'actions',
headerName: 'Actions',
cellRenderer: ActionCellRenderer,
minWidth: 180,
sortable: false,
filter: false,
resizable: false,
suppressMovable: true,
},
]);
const [rowData] = useState([
{ id: 'P001', name: 'Wireless Bluetooth Headphones', category: 'Electronics', price: 79.99, stock: 150, description: 'High-quality audio with active noise cancellation and comfortable earcups.', imageUrl: 'https://via.placeholder.com/50/FF5733/FFFFFF?text=HPC' },
{ id: 'P002', name: 'Organic Green Tea (20 Bags)', category: 'Groceries', price: 9.99, stock: 500, description: 'Premium organic green tea, individually wrapped for freshness.', imageUrl: 'https://via.placeholder.com/50/33FF57/FFFFFF?text=T' },
{ id: 'P003', name: 'Smartphone Tripod Stand', category: 'Accessories', price: 24.50, stock: 230, description: 'Adjustable tripod for smartphones, perfect for vlogging and photography.', imageUrl: 'https://via.placeholder.com/50/3357FF/FFFFFF?text=TPS' },
{ id: 'P004', name: 'Ergonomic Office Chair', category: 'Furniture', price: 249.00, stock: 80, description: 'Designed for comfort and support during long working hours. Adjustable height and recline.', imageUrl: 'https://via.placeholder.com/50/FFFF33/000000?text=OC' },
{ id: 'P005', name: 'Waterproof Fitness Tracker', category: 'Electronics', price: 49.95, stock: 300, description: 'Monitor heart rate, steps, and sleep with this sleek, waterproof fitness tracker.', imageUrl: 'https://via.placeholder.com/50/FF33FF/FFFFFF?text=FT' },
{ id: 'P006', name: 'Stainless Steel Water Bottle', category: 'Kitchenware', price: 18.00, stock: 450, description: 'Durable, insulated water bottle keeps drinks cold for 24 hours and hot for 12 hours.', imageUrl: 'https://via.placeholder.com/50/33FFFF/FFFFFF?text=WB' },
{ id: 'P007', name: 'Leather Wallet', category: 'Accessories', price: 35.00, stock: 120, description: 'Genuine leather wallet with multiple card slots and coin pocket.', imageUrl: 'https://via.placeholder.com/50/FF8833/FFFFFF?text=LW' },
{ id: 'P008', name: 'Yoga Mat', category: 'Sports', price: 29.99, stock: 200, description: 'Non-slip, eco-friendly yoga mat for all types of yoga and fitness routines.', imageUrl: 'https://via.placeholder.com/50/88FF33/FFFFFF?text=YM' },
{ id: 'P009', name: 'Portable SSD 1TB', category: 'Electronics', price: 129.00, stock: 90, description: 'High-speed portable solid-state drive for fast data transfers and backup.', imageUrl: 'https://via.placeholder.com/50/3388FF/FFFFFF?text=SSD' },
{ id: 'P010', name: 'Digital Camera', category: 'Electronics', price: 499.00, stock: 40, description: '24MP DSLR camera with 4K video recording and Wi-Fi connectivity.', imageUrl: 'https://via.placeholder.com/50/FF3388/FFFFFF?text=DC' },
{ id: 'P011', name: 'Coffee Maker', category: 'Kitchenware', price: 59.99, stock: 110, description: 'Programmable coffee maker with a 12-cup capacity and brew strength selector.', imageUrl: 'https://via.placeholder.com/50/8833FF/FFFFFF?text=CM' },
{ id: 'P012', name: 'Desk Lamp with Wireless Charger', category: 'Furniture', price: 45.00, stock: 180, description: 'Modern LED desk lamp with adjustable brightness and an integrated wireless phone charger.', imageUrl: 'https://via.placeholder.com/50/33FF88/FFFFFF?text=DL' },
]);
const defaultColDef = useMemo(() => {
return {
flex: 1,
minWidth: 100,
sortable: true,
filter: true,
resizable: true,
};
}, []);
const getRowHeight = useMemo(() => {
return (params) => {
// Adjust row height dynamically based on content (e.g., description wrapText)
return params.node.isRowMaster ? 240 : 60; // Default for non-master rows, adjust as needed
};
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 700, width: '100%' }}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
pagination={true}
paginationPageSize={10}
getRowHeight={getRowHeight}
></AgGridReact>
</div>
);
};
export default ProductTable;
I've used placeholder image URLs from via.placeholder.com for demonstration purposes. In a real application, these would come from your backend API or CDN. I also added a getRowHeight to ensure rows adjust when wrapText is used for the description.
Beyond the Basics
While this covers the core, AG Grid offers even more for complex e-commerce scenarios:
- Server-Side Row Model: For extremely large product catalogs (thousands to millions of items), fetching data page by page from the server is essential. AG Grid's Server-Side Row Model allows you to offload sorting, filtering, and pagination to your backend.
-
State Management Integration: For a real "Add to Cart" functionality, you'd integrate the
ActionCellRendererwith a global state management solution like Redux, Zustand, or React's Context API to update the user's shopping cart. -
Responsive Design: Consider hiding less critical columns on smaller screens using the
columnApi.setColumnVisible()method or media queries to ensure the table remains usable on mobile devices. - Custom Filters: If the built-in filters aren't enough, you can create custom filter components for highly specific filtering logic (e.g., price range sliders, multi-select category filters).
Conclusion
AG Grid provides an exceptionally powerful and flexible foundation for building sophisticated e-commerce product tables in React. From basic data display to interactive elements like images and action buttons, along with built-in sorting, filtering, and pagination, it covers all the essential requirements. By leveraging custom cell renderers, you can tailor the table to perfectly match your brand's look and user interaction needs, creating an engaging and efficient shopping experience for your customers.