Material React Table is a powerful, feature-rich data table component built on Material-UI and TanStack Table that provides comprehensive functionality including sorting, filtering, pagination, editing, grouping, and virtualization. It combines the flexibility of TanStack Table with Material-UI's design system, offering extensive customization options for building enterprise-grade data management interfaces. This guide walks through implementing advanced table features using Material React Table with React, covering complex configurations, custom renderers, and enterprise-grade implementations. This is part 21 of a series on using Material React Table with React.
Prerequisites
Before you begin, ensure you have:
- Node.js version 16.0 or higher
- npm, yarn, or pnpm package manager
- A React project (version 16.8 or higher) with hooks support
- Advanced understanding of React hooks, context API, and state management
- Material-UI installed (required dependency)
- Familiarity with TypeScript (highly recommended)
- Knowledge of TanStack Table concepts (helpful but not required)
Installation
First, install Material-UI core components (required dependency):
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
Then install Material React Table:
npm install material-react-table
Or install everything with yarn:
yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled material-react-table
Or with pnpm:
pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled material-react-table
Your package.json should include:
{
"dependencies": {
"material-react-table": "^2.0.0",
"@mui/material": "^5.0.0",
"@mui/icons-material": "^5.0.0",
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
Project Setup
Material React Table requires Material-UI's theme provider. Set up your app with the Material-UI theme:
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import App from './App';
const theme = createTheme();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>
);
First Example / Basic Usage
Let's create a basic table component. Create src/DataTable.jsx:
// src/DataTable.jsx
import React, { useMemo } from 'react';
import {
MaterialReactTable,
useMaterialReactTable,
type MRT_ColumnDef,
} from 'material-react-table';
interface Person {
id: number;
name: string;
email: string;
age: number;
city: string;
}
function DataTable() {
const data: Person[] = [
{ id: 1, name: 'John Doe', email: 'john@example.com', age: 28, city: 'New York' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 32, city: 'London' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', age: 45, city: 'Paris' },
{ id: 4, name: 'Alice Williams', email: 'alice@example.com', age: 29, city: 'Tokyo' }
];
const columns = useMemo<MRT_ColumnDef<Person>[]>(
() => [
{
accessorKey: 'id',
header: 'ID',
size: 80,
},
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'email',
header: 'Email',
},
{
accessorKey: 'age',
header: 'Age',
size: 100,
},
{
accessorKey: 'city',
header: 'City',
},
],
[]
);
const table = useMaterialReactTable({
columns,
data,
enableRowSelection: true,
enableColumnOrdering: true,
});
return <MaterialReactTable table={table} />;
}
export default DataTable;
Understanding the Basics
Material React Table uses TanStack Table under the hood with Material-UI styling:
- useMaterialReactTable: Hook that creates table instance with configuration
- MaterialReactTable: Component that renders the table
-
columns: Array of column definitions using
accessorKeyoraccessorFn - data: Array of row data objects (must be stable reference)
- MRT_ColumnDef: TypeScript type for column definitions
Key concepts for advanced usage:
- Stable References: Data and columns should be memoized or stable to prevent re-renders
-
Column Definitions: Use
accessorKeyfor simple access oraccessorFnfor computed values -
Table Instance: Returned from
useMaterialReactTable, provides methods and state -
Feature Flags: Enable/disable features with
enable*props
Here's an example with custom cell rendering:
// src/AdvancedTable.jsx
import React, { useMemo } from 'react';
import {
MaterialReactTable,
useMaterialReactTable,
type MRT_ColumnDef,
} from 'material-react-table';
import { Chip, Box } from '@mui/material';
interface Product {
id: number;
name: string;
category: string;
price: number;
stock: number;
status: string;
}
function AdvancedTable() {
const data: Product[] = [
{ id: 1, name: 'Laptop', category: 'Electronics', price: 999.99, stock: 15, status: 'In Stock' },
{ id: 2, name: 'Mouse', category: 'Electronics', price: 29.99, stock: 8, status: 'Low Stock' },
{ id: 3, name: 'Keyboard', category: 'Electronics', price: 79.99, stock: 12, status: 'In Stock' }
];
const columns = useMemo<MRT_ColumnDef<Product>[]>(
() => [
{
accessorKey: 'id',
header: 'ID',
size: 80,
},
{
accessorKey: 'name',
header: 'Product Name',
},
{
accessorKey: 'category',
header: 'Category',
Cell: ({ cell }) => (
<Chip label={cell.getValue<string>()} size="small" color="primary" />
),
},
{
accessorKey: 'price',
header: 'Price',
Cell: ({ cell }) => `$${cell.getValue<number>().toFixed(2)}`,
},
{
accessorKey: 'stock',
header: 'Stock',
Cell: ({ cell }) => {
const stock = cell.getValue<number>();
return (
<Box
component="span"
sx={{
color: stock < 10 ? 'error.main' : 'success.main',
fontWeight: 'bold',
}}
>
{stock}
</Box>
);
},
},
{
accessorKey: 'status',
header: 'Status',
Cell: ({ cell }) => {
const status = cell.getValue<string>();
return (
<Chip
label={status}
size="small"
color={status === 'In Stock' ? 'success' : 'warning'}
/>
);
},
},
],
[]
);
const table = useMaterialReactTable({
columns,
data,
enableRowSelection: true,
enableSorting: true,
enableFiltering: true,
enablePagination: true,
});
return <MaterialReactTable table={table} />;
}
export default AdvancedTable;
Practical Example / Building Something Real
Let's build a comprehensive admin dashboard with advanced features:
// src/AdminDashboard.jsx
import React, { useMemo, useState, useCallback } from 'react';
import {
MaterialReactTable,
useMaterialReactTable,
type MRT_ColumnDef,
type MRT_Row,
} from 'material-react-table';
import {
Box,
Chip,
IconButton,
Tooltip,
Button,
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import AddIcon from '@mui/icons-material/Add';
interface Employee {
id: number;
name: string;
email: string;
department: string;
salary: number;
startDate: string;
status: string;
}
function AdminDashboard() {
const [data, setData] = useState<Employee[]>([
{
id: 1,
name: 'Sarah Johnson',
email: 'sarah@example.com',
department: 'Engineering',
salary: 95000,
startDate: '2020-01-15',
status: 'Active'
},
{
id: 2,
name: 'Michael Chen',
email: 'michael@example.com',
department: 'Marketing',
salary: 75000,
startDate: '2019-06-20',
status: 'Active'
},
{
id: 3,
name: 'Emily Davis',
email: 'emily@example.com',
department: 'Sales',
salary: 65000,
startDate: '2021-03-10',
status: 'Active'
},
{
id: 4,
name: 'David Wilson',
email: 'david@example.com',
department: 'Engineering',
salary: 110000,
startDate: '2018-09-05',
status: 'Active'
}
]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const columns = useMemo<MRT_ColumnDef<Employee>[]>(
() => [
{
accessorKey: 'id',
header: 'ID',
size: 80,
enableColumnFilter: false,
},
{
accessorKey: 'name',
header: 'Employee Name',
Cell: ({ row }) => (
<Box>
<Box component="span" sx={{ fontWeight: 'bold' }}>
{row.original.name}
</Box>
<Box component="div" sx={{ fontSize: '0.75rem', color: 'text.secondary' }}>
{row.original.email}
</Box>
</Box>
),
},
{
accessorKey: 'department',
header: 'Department',
filterVariant: 'select',
filterSelectOptions: ['Engineering', 'Marketing', 'Sales', 'HR'],
Cell: ({ cell }) => {
const dept = cell.getValue<string>();
const colors: Record<string, 'error' | 'warning' | 'success' | 'info'> = {
Engineering: 'error',
Marketing: 'warning',
Sales: 'success',
HR: 'info',
};
return (
<Chip
label={dept}
size="small"
color={colors[dept] || 'default'}
/>
);
},
},
{
accessorKey: 'salary',
header: 'Salary',
filterVariant: 'range',
Cell: ({ cell, row }) => {
const salary = cell.getValue<number>();
return (
<Box
sx={{
fontWeight: salary > 100000 ? 'bold' : 'normal',
color: salary > 100000 ? 'success.main' : 'inherit',
}}
>
${salary.toLocaleString()}
</Box>
);
},
},
{
accessorKey: 'startDate',
header: 'Start Date',
filterVariant: 'date',
Cell: ({ cell }) => {
const date = new Date(cell.getValue<string>());
return date.toLocaleDateString('en-US');
},
},
{
accessorKey: 'status',
header: 'Status',
filterVariant: 'select',
filterSelectOptions: ['Active', 'Inactive'],
Cell: ({ cell }) => {
const status = cell.getValue<string>();
return (
<Chip
label={status}
size="small"
color={status === 'Active' ? 'success' : 'error'}
/>
);
},
},
],
[]
);
const handleDeleteRow = useCallback(
(row: MRT_Row<Employee>) => {
if (window.confirm(`Are you sure you want to delete ${row.original.name}?`)) {
setData((prev) => prev.filter((item) => item.id !== row.original.id));
}
},
[]
);
const handleBulkDelete = useCallback(() => {
const selectedIds = Object.keys(rowSelection).map(Number);
if (selectedIds.length === 0) return;
if (window.confirm(`Delete ${selectedIds.length} selected employees?`)) {
setData((prev) => prev.filter((item) => !selectedIds.includes(item.id)));
setRowSelection({});
}
}, [rowSelection]);
const table = useMaterialReactTable({
columns,
data,
enableRowSelection: true,
enableColumnOrdering: true,
enableGlobalFilter: true,
enableColumnFilters: true,
enablePagination: true,
enableSorting: true,
enableDensityToggle: false,
enableFullScreenToggle: true,
enableStickyHeader: true,
muiTableContainerProps: { sx: { maxHeight: '600px' } },
onRowSelectionChange: setRowSelection,
state: { rowSelection },
renderTopToolbarCustomActions: ({ table }) => (
<Box sx={{ display: 'flex', gap: '8px', p: '8px' }}>
<Button
color="primary"
startIcon={<AddIcon />}
onClick={() => console.log('Add new employee')}
variant="contained"
>
Add Employee
</Button>
{Object.keys(rowSelection).length > 0 && (
<Button
color="error"
startIcon={<DeleteIcon />}
onClick={handleBulkDelete}
variant="contained"
>
Delete Selected ({Object.keys(rowSelection).length})
</Button>
)}
</Box>
),
renderRowActions: ({ row }) => (
<Box sx={{ display: 'flex', gap: '4px' }}>
<Tooltip title="Edit">
<IconButton onClick={() => console.log('Edit', row.original)}>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton color="error" onClick={() => handleDeleteRow(row)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</Box>
),
initialState: {
showColumnFilters: true,
showGlobalFilter: true,
pagination: { pageSize: 10, pageIndex: 0 },
},
});
return (
<Box sx={{ p: 3 }}>
<MaterialReactTable table={table} />
</Box>
);
}
export default AdminDashboard;
This advanced example demonstrates:
- Row selection with bulk operations
- Custom cell rendering with Material-UI components
- Advanced filtering (select, range, date filters)
- Custom toolbar actions
- Row actions (edit, delete)
- Conditional styling
- Column ordering
- Global search
- Sticky header
- Full screen toggle
Common Issues / Troubleshooting
Table not rendering: Ensure you've wrapped your app with Material-UI's
ThemeProviderand imported the necessary CSS. Verify thatdataandcolumnsare stable references (useuseMemooruseState).Performance issues: Always memoize
columnswithuseMemoand ensuredatais a stable reference. Avoid creating new arrays/objects on every render.TypeScript errors: Material React Table has excellent TypeScript support. Use
MRT_ColumnDef<YourType>[]for columns and define your data types properly.Filtering not working: Enable
enableColumnFilters: trueand set appropriatefilterVariantin column definitions ('text', 'select', 'range', 'date').Row selection not working: Enable
enableRowSelection: trueand manage selection state withonRowSelectionChangeandstate.rowSelection.
Next Steps
Now that you've mastered Material React Table:
- Explore advanced features like column virtualization and grouping
- Implement custom cell editors and validators
- Add server-side data loading and pagination
- Learn about column resizing, pinning, and reordering
- Explore expandable rows and detail panels
- Add export functionality (CSV, Excel, PDF)
- Check the official repository: https://github.com/KevinVandy/material-react-table
- Look for part 22 of this series for more advanced topics
Summary
You've learned how to implement advanced data table features with Material React Table, including sorting, filtering, pagination, row selection, and custom rendering using Material-UI components. The library provides extensive functionality for building enterprise-grade data management interfaces with excellent performance and extensive customization options.
SEO Keywords
material-react-table
Material React Table tutorial
React data table Material-UI
material-react-table installation
React table component advanced
material-react-table example
React data grid Material-UI
material-react-table setup
React interactive table
Material React Table filtering
React table component
material-react-table pagination
React Material-UI table
Material React Table sorting
React enterprise data table
Top comments (0)