DEV Community

Oliver Reed
Oliver Reed

Posted on

Advanced Data Table Implementation with Material React Table

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
Enter fullscreen mode Exit fullscreen mode

Then install Material React Table:

npm install material-react-table
Enter fullscreen mode Exit fullscreen mode

Or install everything with yarn:

yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled material-react-table
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled material-react-table
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 accessorKey or accessorFn
  • 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 accessorKey for simple access or accessorFn for 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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

  1. Table not rendering: Ensure you've wrapped your app with Material-UI's ThemeProvider and imported the necessary CSS. Verify that data and columns are stable references (use useMemo or useState).

  2. Performance issues: Always memoize columns with useMemo and ensure data is a stable reference. Avoid creating new arrays/objects on every render.

  3. TypeScript errors: Material React Table has excellent TypeScript support. Use MRT_ColumnDef<YourType>[] for columns and define your data types properly.

  4. Filtering not working: Enable enableColumnFilters: true and set appropriate filterVariant in column definitions ('text', 'select', 'range', 'date').

  5. Row selection not working: Enable enableRowSelection: true and manage selection state with onRowSelectionChange and state.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)