AG Grid React: The Only Guide You’ll Need to Build Serious Data Tables
If you’ve ever tried to render a table with ten thousand rows in React and watched your browser
quietly give up on life, you already understand why AG Grid
exists. It is, without exaggeration, one of the most capable
React data grid libraries available today — free for most use cases, battle-tested
at enterprise scale, and surprisingly straightforward to wire up. This guide covers everything from
AG Grid installation to cell editing, so you can go from zero to a fully
interactive, paginated, sortable, and filterable table in a single afternoon.
We’ll use the Community edition throughout, which is MIT-licensed and covers 95% of real-world
needs. No upsell, no gotchas. Just a genuinely excellent
React table component with first-class documentation and a
responsive GitHub community.
Why AG Grid Beats Most React Table Libraries
The React ecosystem is not exactly short on table solutions. There’s TanStack Table (formerly
React Table), MUI DataGrid, React Data Table Component, and a dozen others you’ve bookmarked and
forgotten. So why AG Grid? The short answer is performance at scale. AG Grid implements
row virtualisation by default, rendering only the rows currently visible in the
viewport. Feed it a million-row dataset and it will shrug indifferently while a naive implementation
would detonate your user’s RAM.
Beyond raw performance, the API surface is remarkably consistent. You define
rowData and columnDefs, drop in <AgGridReact />, and
most things just work. Sorting, filtering, and resizable columns come enabled out of the box — no
plugin hunts, no third-party middleware. The interactive React table you’d spend a
week configuring with TanStack takes about thirty minutes with AG Grid, and that’s including the
time to make coffee.
The enterprise tier adds row grouping, server-side row models, Excel export, and integrated charts —
powerful stuff, but the Community version is already a fully capable
React data grid library for building dashboards, admin panels, analytics tools,
and anything else that requires displaying structured data at volume. The architecture is the same
under both licences; you’re not getting a lobotomised community edition. You’re just getting the
core engine, which is still remarkable.
AG Grid Installation in a React Project
AG Grid installation is two packages: the core grid engine and the React wrapper.
That’s it. Open a terminal in your project root and run the following — or swap
npm for yarn / pnpm as your religion dictates:
npm install ag-grid-community ag-grid-react
Once installed, import the required CSS. AG Grid ships with two built-in themes: Alpine
(clean and modern) and Balham (denser, spreadsheet-like). You’ll want one of these
globally, typically in your main.tsx or index.tsx:
// In your entry file or global stylesheet
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
If you’re using Vite, Next.js, or Create React App, no additional bundler configuration is needed.
AG Grid is tree-shakeable from version 30+ using the ModuleRegistry pattern if you
want to trim bundle size — relevant for large apps where every kilobyte matters. For most projects,
the default import is fine and you can optimise later when you have an actual reason to.
@types/ag-grid-* packages needed. Everything is included inag-grid-community.
Building Your First AG Grid React Example
Let’s build a minimal but complete AG Grid React example — a product inventory
table. We’ll keep the data inline for clarity, then evolve it progressively. The key mental model
is this: rowData is your dataset (an array of objects), and
columnDefs describes how to display each field. Everything else is configuration on
top of those two primitives.
// ProductGrid.tsx
import React, { useState } from 'react';
import { AgGridReact } from 'ag-grid-react';
import { ColDef } from 'ag-grid-community';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
interface Product {
id: number;
name: string;
category: string;
price: number;
stock: number;
}
const rowData: Product[] = [
{ id: 1, name: 'Wireless Keyboard', category: 'Electronics', price: 49.99, stock: 142 },
{ id: 2, name: 'Ergonomic Mouse', category: 'Electronics', price: 34.99, stock: 89 },
{ id: 3, name: 'Standing Desk', category: 'Furniture', price: 429.00, stock: 23 },
{ id: 4, name: 'USB-C Hub', category: 'Electronics', price: 29.99, stock: 215 },
{ id: 5, name: 'Monitor Arm', category: 'Furniture', price: 89.99, stock: 57 },
{ id: 6, name: 'Noise Cancelling Headphones', category: 'Electronics', price: 249.99, stock: 38 },
];
const columnDefs: ColDef<Product>[] = [
{ field: 'id', headerName: 'ID', width: 80 },
{ field: 'name', headerName: 'Product', flex: 2 },
{ field: 'category', headerName: 'Category', flex: 1 },
{ field: 'price', headerName: 'Price ($)', flex: 1,
valueFormatter: (p) => `$${p.value.toFixed(2)}` },
{ field: 'stock', headerName: 'In Stock', flex: 1 },
];
const defaultColDef: ColDef = {
sortable: true,
filter: true,
resizable: true,
};
export default function ProductGrid() {
return (
<div className="ag-theme-alpine" style={{ height: 400, width: '100%' }}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
animateRows={true}
/>
</div>
);
}
Three observations worth noting here. First, defaultColDef is your best friend — it
applies properties to every column without repetition. Second, flex on column
definitions behaves exactly like CSS flexbox: columns divide available space proportionally.
Third, valueFormatter is a clean way to format display values without mutating your
data. The underlying price remains a number; AG Grid just renders it
prettier.
Run your app and you’ll see a fully functional React data table with working
column filters (click the funnel icon in each header), column sorting on click, and resizable
column boundaries — all from about 40 lines of code. If that feels like cheating, it’s because
the library has already done the hard parts.
AG Grid Filtering and Sorting: Beyond the Defaults
The AG Grid filtering and sorting system is far deeper than the default column
header behaviour suggests. By default, each column gets a text filter. But AG Grid ships with
specialised filter types: agNumberColumnFilter for numeric ranges,
agDateColumnFilter for date pickers, and agSetColumnFilter (Enterprise)
for checkbox-style multi-select filtering. Switching filter type is a one-liner on the column
definition:
{ field: 'price', filter: 'agNumberColumnFilter', filterParams: {
buttons: ['reset', 'apply'],
closeOnApply: true,
} },
{ field: 'stock', filter: 'agNumberColumnFilter' },
Sorting behaviour is equally configurable. By default, columns cycle through ascending →
descending → no sort on repeated clicks. You can lock sort direction, define custom comparators,
or implement multi-column sorting by holding Shift while clicking headers — that last
feature works automatically with no configuration required. For complex cases, define a
comparator function directly on the column definition, receiving the two cell values
and returning a standard -1 | 0 | 1 comparison result.
One underused feature is the Quick Filter — a global search input that filters
across all columns simultaneously, like a spreadsheet’s Ctrl+F. Wire it up with a single prop:
const [quickFilterText, setQuickFilterText] = useState('');
// In your JSX:
<input
type="text"
placeholder="Search all columns..."
onChange={(e) => setQuickFilterText(e.target.value)}
/>
<AgGridReact
quickFilterText={quickFilterText}
{/* ...other props */}
/>
This produces an instant, client-side full-text search across your entire dataset with zero
additional logic. For server-side filtering (when your dataset lives behind an API), AG Grid’s
onFilterChanged callback gives you the current filter model as a plain object, which
you can serialize and send to your backend. The grid stays agnostic about where data comes from;
you handle the fetch, it handles the render.
AG Grid Pagination: Handling Large Datasets Gracefully
AG Grid pagination is genuinely one of the most concise features in the library.
Three props and you’re done. The grid automatically manages page state, renders navigation
controls at the bottom, and respects your active sort and filter state — meaning pagination is
applied after filtering, which is exactly what users expect:
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
pagination={true}
paginationPageSize={15}
paginationPageSizeSelector={[10, 15, 25, 50]}
/>
The paginationPageSizeSelector prop (available from AG Grid 30.1+) renders a
dropdown that lets users control page density — a small UX detail that makes a measurable
difference in user satisfaction for data-heavy applications. For server-side pagination, you
disable the client-side model and implement the IServerSideDatasource interface,
but that’s a topic for a dedicated deep-dive rather than this introductory guide.
Something worth knowing: when pagination is enabled alongside
domLayout="autoHeight", the grid adjusts its height to match the current page size.
This means your layout won’t have an awkward fixed-height box sitting there half-empty when
filtered results return fewer than the page size. Small detail, significant visual improvement.
AG Grid sweats these kinds of details so you don’t have to.
AG Grid Cell Editing: Making Your Table Interactive
Most React table libraries treat data as read-only — you display it, you don’t edit it inline. AG
Grid takes a different view: inline cell editing is a first-class feature that
turns your React data grid into something closer to a
React spreadsheet table. Enabling it is, predictably, straightforward. Add
editable: true to any column definition, and clicking that cell renders an input.
When the user presses Enter or clicks away, the value is committed back to the row data:
const columnDefs: ColDef[] = [
{ field: 'name', editable: true },
{ field: 'price', editable: true, cellEditor: 'agNumberCellEditor' },
{ field: 'stock', editable: true, cellEditor: 'agNumberCellEditor' },
// Read-only columns simply omit 'editable'
{ field: 'category' },
];
Out of the box, AG Grid ships several cell editors: agTextCellEditor (default),
agNumberCellEditor, agSelectCellEditor for dropdowns, and
agLargeTextCellEditor for multi-line text. For custom requirements — say, a date
picker or an autocomplete component — you implement the ICellEditorComp interface
and register your React component. It’s a clean contract: AG Grid calls
getValue() when it wants the committed value, and you return whatever your
component has collected.
Tracking edits programmatically is done through the onCellValueChanged callback,
which receives the old value, the new value, the row node, and the column definition. This is
where you’d trigger an API call to persist the change, show a toast notification, or update
a local state store. Combined with valueSetter on the column definition, you can
intercept changes before they hit the row data — useful for validation logic:
<AgGridReact
onCellValueChanged={(event) => {
console.log(`${event.colDef.field} changed:
${event.oldValue} → ${event.newValue}`);
// Trigger your API call here
saveProductUpdate(event.data);
}}
/>
Practical Patterns That Save You Hours
After the basics are wired up, a few patterns consistently appear in production AG Grid
implementations. The first is custom cell renderers. AG Grid’s
cellRenderer prop accepts any React component, which means you can render badges,
progress bars, action buttons, or sparklines directly inside table cells. This is what separates
a functional React grid component from one that actually impresses stakeholders:
// StatusBadge.tsx — a custom cell renderer
import { ICellRendererParams } from 'ag-grid-community';
export function StockRenderer(params: ICellRendererParams) {
const stock = params.value as number;
const colour = stock > 100 ? '#22c55e' : stock > 20 ? '#f59e0b' : '#ef4444';
return (
<span style={{
background: colour,
color: 'white',
borderRadius: '4px',
padding: '2px 8px',
fontSize: '0.8rem',
fontWeight: 600,
}}>
{stock}
</span>
);
}
// In your columnDefs:
{ field: 'stock', cellRenderer: StockRenderer }
The second pattern is using the Grid API via a ref. AG Grid exposes
a comprehensive imperative API through gridRef.current.api. You can programmatically
sort columns, set filter models, export to CSV, select rows, scroll to a specific row, and
dozens of other operations that would be painful to orchestrate through props alone. In complex
dashboards where external controls need to manipulate the grid state, this is invaluable.
The third pattern — and the one most developers discover too late — is
row selection. AG Grid supports both single and multi-row selection with
checkboxes. Combine this with the Grid API’s getSelectedRows() method and you
have a complete pattern for bulk operations: the user selects rows, clicks a “Delete Selected”
or “Export Selected” button, and your handler pulls the selection cleanly. No parallel state
arrays to keep in sync, no index juggling. The grid manages selection state internally and
exposes it on demand.
- rowSelection=”multiple” — enables multi-row selection via checkboxes or click
- suppressRowClickSelection={true} — prevents accidental row selection on cell click
- checkboxSelection: true on a column def — renders the selection checkbox
Performance Considerations for Real-World Applications
For datasets under 10,000 rows, AG Grid’s default client-side row model handles everything
without any tuning. Beyond that, two features become relevant: rowBuffer (how many
rows outside the viewport to pre-render) and suppressColumnVirtualisation (disabled
by default, meaning columns outside the viewport are also virtualised). Both defaults are sane;
you only touch them when profiling reveals a specific bottleneck.
For truly large datasets — think financial ticks, log streams, or sensor data — AG Grid’s
Infinite Row Model and Server-Side Row Model (the latter being
an Enterprise feature) allow you to load data in blocks as the user scrolls, keeping memory
consumption flat regardless of total dataset size. The Community-available Infinite Row Model
is sufficient for most pagination-via-scroll use cases and requires implementing a simple
IDatasource interface with a getRows() method.
One performance trap: avoid creating new columnDefs and rowData arrays
on every render. Define them outside your component or memoize them with useMemo.
AG Grid does deep equality checking on row data changes, but creating a new array reference on
every render forces unnecessary reconciliation work. This is a React fundamentals issue rather
than an AG Grid one, but it surfaces clearly with large grids where the performance cost is
magnified.
Frequently Asked Questions
Yes. AG Grid Community
is completely free and open-source under the MIT licence. It includes all core features —
sorting, filtering, pagination, cell editing, and virtual scrolling — with no usage limits.
AG Grid Enterprise adds advanced features (row grouping, pivoting, Excel export, integrated
charts) and requires a paid commercial licence. For the majority of projects, Community is
everything you need.
Add pagination={true} and paginationPageSize={20} as props directly
to your <AgGridReact /> component. AG Grid automatically renders page
navigation controls and applies pagination after any active filters and sorts. No additional
state management or third-party libraries required.
Absolutely. AG Grid uses row and column virtualisation by default, rendering only what’s
visible in the viewport. This makes it capable of displaying hundreds of thousands of rows
without significant performance degradation. For truly massive or streaming datasets, the
Infinite Row Model (Community) and Server-Side Row Model (Enterprise) allow block-based
data loading that keeps memory consumption constant regardless of total dataset size.

Add comment