I've been building data-heavy mobile apps lately and spent way too much time evaluating table libraries. The React ecosystem has some solid options, but they're wildly different in approach. Let me break down what I've learned from actually shipping these in production.
TanStack Table (React Table v8): The Headless Powerhouse
TanStack Table is what React Table became after v7. It's completely headless, meaning zero UI components included. At first this felt like extra work, but honestly? It's perfect for mobile-first development where you need total control over rendering.
The core philosophy here is that TanStack handles the state management and logic while you handle every pixel of the UI. This makes it incredibly flexible but requires more initial setup.
1import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
2
3function DataTable({ data }) {
4 const columns = [
5 {
6 accessorKey: 'name',
7 header: 'Name',
8 cell: info => info.getValue(),
9 },
10 {
11 accessorKey: 'status',
12 header: 'Status',
13 cell: info => (
14 <span className={`status-${info.getValue()}`}>
15 {info.getValue()}
16 </span>
17 ),
18 },
19 ];
20
21 const table = useReactTable({
22 data,
23 columns,
24 getCoreRowModel: getCoreRowModel(),
25 });
26
27 return (
28 <div className="mobile-table">
29 {table.getRowModel().rows.map(row => (
30 <div key={row.id} className="table-row">
31 {row.getVisibleCells().map(cell => (
32 <div key={cell.id} className="table-cell">
33 {flexRender(cell.column.columnDef.cell, cell.getContext())}
34 </div>
35 ))}
36 </div>
37 ))}
38 </div>
39 );
40}The API is hook-based and composable. Want sorting? Add getSortedRowModel(). Need filtering? Toss in getFilteredRowModel(). I've found this modular approach keeps bundle sizes reasonable - you only ship what you actually use.
The mobile catch: On smaller screens, traditional tables suck. With TanStack's headless approach, I can easily switch to card layouts or accordions based on viewport width without fighting against built-in styles. The same table logic works whether I'm rendering a desktop grid or mobile cards.
Performance-wise, it handles 10k+ rows without breaking a sweat if you enable virtualization. The column pinning feature is surprisingly smooth even on mid-range Android devices.
AG Grid: The Enterprise Beast
AG Grid is what you reach for when your PM says "we need Excel but in the browser." It's massive, feature-complete, and honestly overkill for most mobile apps. But sometimes you need that firepower.
The free community version is already packed with features. The enterprise version adds stuff like row grouping, aggregation, and server-side operations that become essential at scale.
1import { AgGridReact } from 'ag-grid-react';
2import 'ag-grid-community/styles/ag-grid.css';
3import 'ag-grid-community/styles/ag-theme-alpine.css';
4
5function OrdersTable({ orders }) {
6 const columnDefs = [
7 {
8 field: 'orderId',
9 filter: 'agTextColumnFilter',
10 sortable: true,
11 },
12 {
13 field: 'total',
14 filter: 'agNumberColumnFilter',
15 valueFormatter: params => `$${params.value.toFixed(2)}`,
16 },
17 {
18 field: 'status',
19 cellRenderer: params => {
20 const statusClass = `status-badge status-${params.value}`;
21 return `<span class="${statusClass}">${params.value}</span>`;
22 },
23 },
24 ];
25
26 return (
27 <div className="ag-theme-alpine" style={{ height: 600 }}>
28 <AgGridReact
29 rowData={orders}
30 columnDefs={columnDefs}
31 pagination={true}
32 paginationPageSize={50}
33 domLayout='autoHeight'
34 />
35 </div>
36 );
37}Here's the thing about AG Grid on mobile: it's not really built for it. The default experience on a phone is pretty rough. You can make it work with custom cell renderers and responsive column definitions, but you're fighting the library's desktop-first assumptions.
Where AG Grid shines is when you have power users who need to manipulate large datasets. The Excel-like filtering and sorting is incredibly powerful. I used it for an admin dashboard where users needed to export filtered data to CSV - that worked beautifully.
Bundle size is the elephant in the room. The community version adds ~150KB gzipped to your bundle. Enterprise is even larger. For mobile-first apps, that's a tough sell unless you really need those features.
React Data Grid: The Modern Middle Ground
React Data Grid from Adazzle (formerly known as react-data-grid) hit a sweet spot for me recently. It's less opinionated than AG Grid but more batteries-included than TanStack Table.
1import DataGrid from 'react-data-grid';
2import 'react-data-grid/lib/styles.css';
3
4function ProductGrid({ products }) {
5 const columns = [
6 { key: 'id', name: 'ID', frozen: true },
7 { key: 'name', name: 'Product', resizable: true },
8 {
9 key: 'price',
10 name: 'Price',
11 formatter: ({ row }) => `$${row.price.toFixed(2)}`,
12 },
13 {
14 key: 'actions',
15 name: 'Actions',
16 formatter: ({ row }) => (
17 <button onClick={() => editProduct(row.id)}>
18 Edit
19 </button>
20 ),
21 },
22 ];
23
24 return (
25 <DataGrid
26 columns={columns}
27 rows={products}
28 style={{ height: 500 }}
29 onRowsChange={setProducts}
30 />
31 );
32}The built-in row selection, inline editing, and column resizing work out of the box. No configuration hell. The frozen columns feature is genuinely useful when you need to keep an identifier visible while scrolling horizontally.
Performance is solid up to about 5k rows without virtualization. Beyond that, you'll want to implement pagination or infinite scroll. The library uses CSS Grid internally which makes the rendering pretty snappy.
Mobile support is decent but not amazing. The touch interactions work, but you'll need custom CSS to make it truly mobile-friendly. I ended up hiding certain columns on mobile and adding a details modal for the full data.
Material React Table: When You Need MUI Integration
If you're already using Material-UI, Material React Table (MRT) is worth considering. It's built on top of TanStack Table but adds MUI components and a bunch of features out of the box.
1import MaterialReactTable from 'material-react-table';
2
3function UsersTable({ users }) {
4 const columns = [
5 {
6 accessorKey: 'email',
7 header: 'Email',
8 size: 200,
9 },
10 {
11 accessorKey: 'role',
12 header: 'Role',
13 filterVariant: 'select',
14 filterSelectOptions: ['admin', 'user', 'guest'],
15 },
16 {
17 accessorKey: 'lastLogin',
18 header: 'Last Login',
19 Cell: ({ cell }) => new Date(cell.getValue()).toLocaleDateString(),
20 },
21 ];
22
23 return (
24 <MaterialReactTable
25 columns={columns}
26 data={users}
27 enableRowSelection
28 enableColumnFilters
29 enablePagination
30 muiTablePaperProps={{
31 elevation: 0,
32 sx: { borderRadius: '8px' },
33 }}
34 />
35 );
36}MRT gives you a ton of features with minimal config: global search, column filters, density toggle, fullscreen mode. The MUI integration means everything looks consistent with your existing design system.
The downside? Bundle size is hefty since you're pulling in both TanStack Table and MUI components. I measured around 200KB gzipped for a basic table. That's a lot for mobile users on slow connections.
The mobile story here depends entirely on how well MUI's responsive components work for your use case. The built-in density toggle helps - switching to compact mode makes tables more usable on smaller screens.
What I Actually Use
For mobile-first apps, I default to TanStack Table. The headless approach lets me build exactly what I need without fighting against desktop-centric defaults. Yeah, it takes longer to set up initially, but the flexibility is worth it when you need to switch between table and card layouts.
For admin dashboards or internal tools where users are on desktop, AG Grid is hard to beat if budget allows for the enterprise license. The feature set is unmatched.
React Data Grid hits a nice balance for projects that need something quick but customizable. I used it for a recent project where we needed inline editing and it saved days of development time.
Material React Table makes sense if you're already deep in the MUI ecosystem and need tables that match your existing design system. Just watch that bundle size.
The real trick is matching the library to your actual requirements. Don't reach for AG Grid if you just need to display 50 rows with basic sorting. And don't torture yourself with a fully custom TanStack implementation if React Data Grid gives you 90% of what you need out of the box.
