DEV Community

Nathan Collins
Nathan Collins

Posted on

Building Feature-Rich Tables with Material-Table in React

Material-Table is a powerful data table component for React built on Material-UI that provides comprehensive features including sorting, filtering, pagination, editing, exporting, and more out of the box. It offers a simple API while delivering enterprise-grade functionality, making it perfect for building complex data management interfaces. This guide walks through creating advanced, interactive data tables using Material-Table with React, covering setup, configuration, and practical implementation patterns. This is part 11 of a series on using Material-Table with React.

Prerequisites

Before you begin, ensure you have:

  • Node.js version 14.0 or higher
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) with hooks support
  • Basic understanding of React hooks (useState, useCallback)
  • Material-UI installed (required dependency)
  • Familiarity with JavaScript/TypeScript

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-Table:

npm install material-table
Enter fullscreen mode Exit fullscreen mode

Or install everything with yarn:

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

Or with pnpm:

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

Your package.json should include:

{
  "dependencies": {
    "material-table": "^1.69.3",
    "@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-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, { useState } from 'react';
import MaterialTable from 'material-table';

function DataTable() {
  const [data, setData] = useState([
    { 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 = [
    { title: 'ID', field: 'id', type: 'numeric' },
    { title: 'Name', field: 'name' },
    { title: 'Email', field: 'email' },
    { title: 'Age', field: 'age', type: 'numeric' },
    { title: 'City', field: 'city' }
  ];

  return (
    <div style={{ padding: '20px' }}>
      <MaterialTable
        title="Employee Directory"
        columns={columns}
        data={data}
        options={{
          search: true,
          paging: true,
          filtering: true,
          exportButton: true
        }}
      />
    </div>
  );
}

export default DataTable;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import { Container, Typography, Box } from '@mui/material';
import DataTable from './DataTable';
import './App.css';

function App() {
  return (
    <Container maxWidth="lg">
      <Box sx={{ my: 4 }}>
        <Typography variant="h4" component="h1" gutterBottom>
          Material-Table Example
        </Typography>
        <DataTable />
      </Box>
    </Container>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a fully functional table with:

  • Built-in search functionality
  • Pagination
  • Column filtering
  • Export button (CSV, PDF)
  • Sorting (click column headers)
  • Material-UI styling

Understanding the Basics

Material-Table uses a column-based configuration where:

  • title: Column header text
  • field: Property key in your data objects
  • type: Data type ('string', 'numeric', 'boolean', 'date', etc.)
  • data: Array of row data objects

Key concepts:

  • Columns: Define table structure and behavior
  • Data: Array of objects representing rows
  • Options: Enable/disable features like search, pagination, filtering
  • Actions: Custom action buttons (add, edit, delete)
  • Icons: Material-UI icons for actions and features

Here's an example with editable rows and actions:

// src/EditableTable.jsx
import React, { useState } from 'react';
import MaterialTable from 'material-table';
import { forwardRef } from 'react';
import AddBox from '@mui/icons-material/AddBox';
import ArrowDownward from '@mui/icons-material/ArrowDownward';
import Check from '@mui/icons-material/Check';
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import ChevronRight from '@mui/icons-material/ChevronRight';
import Clear from '@mui/icons-material/Clear';
import DeleteOutline from '@mui/icons-material/DeleteOutline';
import Edit from '@mui/icons-material/Edit';
import FilterList from '@mui/icons-material/FilterList';
import FirstPage from '@mui/icons-material/FirstPage';
import LastPage from '@mui/icons-material/LastPage';
import Remove from '@mui/icons-material/Remove';
import SaveAlt from '@mui/icons-material/SaveAlt';
import Search from '@mui/icons-material/Search';
import ViewColumn from '@mui/icons-material/ViewColumn';

const tableIcons = {
  Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />),
  Check: forwardRef((props, ref) => <Check {...props} ref={ref} />),
  Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />),
  DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />),
  Export: forwardRef((props, ref) => <SaveAlt {...props} ref={ref} />),
  Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />),
  ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />),
  ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref} />),
  ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />)
};

function EditableTable() {
  const [data, setData] = useState([
    { id: 1, name: 'Laptop', category: 'Electronics', price: 999.99, stock: 15 },
    { id: 2, name: 'Mouse', category: 'Electronics', price: 29.99, stock: 8 },
    { id: 3, name: 'Keyboard', category: 'Electronics', price: 79.99, stock: 12 }
  ]);

  const columns = [
    { title: 'ID', field: 'id', type: 'numeric', editable: 'never' },
    { title: 'Name', field: 'name' },
    { title: 'Category', field: 'category' },
    { title: 'Price', field: 'price', type: 'currency' },
    { title: 'Stock', field: 'stock', type: 'numeric' }
  ];

  return (
    <MaterialTable
      title="Product Inventory"
      columns={columns}
      data={data}
      icons={tableIcons}
      editable={{
        onRowAdd: (newData) =>
          new Promise((resolve) => {
            setTimeout(() => {
              setData([...data, { ...newData, id: data.length + 1 }]);
              resolve();
            }, 600);
          }),
        onRowUpdate: (newData, oldData) =>
          new Promise((resolve) => {
            setTimeout(() => {
              const dataUpdate = [...data];
              const index = oldData.tableData.id;
              dataUpdate[index] = newData;
              setData([...dataUpdate]);
              resolve();
            }, 600);
          }),
        onRowDelete: (oldData) =>
          new Promise((resolve) => {
            setTimeout(() => {
              const dataDelete = [...data];
              const index = oldData.tableData.id;
              dataDelete.splice(index, 1);
              setData([...dataDelete]);
              resolve();
            }, 600);
          })
      }}
      options={{
        actionsColumnIndex: -1,
        addRowPosition: 'first'
      }}
    />
  );
}

export default EditableTable;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive employee management system:

// src/EmployeeManagement.jsx
import React, { useState } from 'react';
import MaterialTable from 'material-table';
import { forwardRef } from 'react';
import AddBox from '@mui/icons-material/AddBox';
import ArrowDownward from '@mui/icons-material/ArrowDownward';
import Check from '@mui/icons-material/Check';
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import ChevronRight from '@mui/icons-material/ChevronRight';
import Clear from '@mui/icons-material/Clear';
import DeleteOutline from '@mui/icons-material/DeleteOutline';
import Edit from '@mui/icons-material/Edit';
import FilterList from '@mui/icons-material/FilterList';
import FirstPage from '@mui/icons-material/FirstPage';
import LastPage from '@mui/icons-material/LastPage';
import Remove from '@mui/icons-material/Remove';
import SaveAlt from '@mui/icons-material/SaveAlt';
import Search from '@mui/icons-material/Search';
import ViewColumn from '@mui/icons-material/ViewColumn';

const tableIcons = {
  Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />),
  Check: forwardRef((props, ref) => <Check {...props} ref={ref} />),
  Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />),
  DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />),
  Export: forwardRef((props, ref) => <SaveAlt {...props} ref={ref} />),
  Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />),
  ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />),
  ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref} />),
  ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />)
};

function EmployeeManagement() {
  const [data, setData] = useState([
    { 
      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'
    }
  ]);

  const columns = [
    { 
      title: 'ID', 
      field: 'id', 
      type: 'numeric',
      editable: 'never',
      width: 80
    },
    { 
      title: 'Name', 
      field: 'name',
      validate: rowData => rowData.name === undefined || rowData.name === '' ? 'Name is required' : true
    },
    { 
      title: 'Email', 
      field: 'email',
      validate: rowData => {
        if (!rowData.email) return 'Email is required';
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(rowData.email) ? true : 'Invalid email format';
      }
    },
    { 
      title: 'Department', 
      field: 'department',
      lookup: {
        Engineering: 'Engineering',
        Marketing: 'Marketing',
        Sales: 'Sales',
        HR: 'HR'
      }
    },
    { 
      title: 'Salary', 
      field: 'salary', 
      type: 'currency',
      currencySetting: { currencyCode: 'USD', minimumFractionDigits: 0 }
    },
    { 
      title: 'Start Date', 
      field: 'startDate', 
      type: 'date'
    },
    { 
      title: 'Status', 
      field: 'status',
      lookup: {
        Active: 'Active',
        Inactive: 'Inactive'
      }
    }
  ];

  return (
    <div style={{ padding: '20px' }}>
      <MaterialTable
        title="Employee Management"
        columns={columns}
        data={data}
        icons={tableIcons}
        editable={{
          onRowAdd: (newData) =>
            new Promise((resolve, reject) => {
              setTimeout(() => {
                // Validate required fields
                if (!newData.name || !newData.email) {
                  reject();
                  return;
                }
                setData([...data, { ...newData, id: data.length + 1 }]);
                resolve();
              }, 600);
            }),
          onRowUpdate: (newData, oldData) =>
            new Promise((resolve, reject) => {
              setTimeout(() => {
                if (!newData.name || !newData.email) {
                  reject();
                  return;
                }
                const dataUpdate = [...data];
                const index = oldData.tableData.id;
                dataUpdate[index] = newData;
                setData([...dataUpdate]);
                resolve();
              }, 600);
            }),
          onRowDelete: (oldData) =>
            new Promise((resolve) => {
              setTimeout(() => {
                const dataDelete = [...data];
                const index = oldData.tableData.id;
                dataDelete.splice(index, 1);
                setData([...dataDelete]);
                resolve();
              }, 600);
            })
        }}
        options={{
          search: true,
          paging: true,
          filtering: true,
          exportButton: true,
          exportAllData: true,
          grouping: true,
          actionsColumnIndex: -1,
          addRowPosition: 'first',
          pageSize: 10,
          pageSizeOptions: [5, 10, 20, 50]
        }}
        localization={{
          body: {
            emptyDataSourceMessage: 'No records to display',
            addTooltip: 'Add',
            deleteTooltip: 'Delete',
            editTooltip: 'Edit',
            filterRow: {
              filterTooltip: 'Filter'
            }
          },
          header: {
            actions: 'Actions'
          },
          pagination: {
            labelRowsSelect: 'rows',
            labelRowsPerPage: 'Rows per page:',
            firstAriaLabel: 'First Page',
            firstTooltip: 'First Page',
            previousAriaLabel: 'Previous Page',
            previousTooltip: 'Previous Page',
            nextAriaLabel: 'Next Page',
            nextTooltip: 'Next Page',
            lastAriaLabel: 'Last Page',
            lastTooltip: 'Last Page'
          },
          toolbar: {
            searchTooltip: 'Search',
            searchPlaceholder: 'Search'
          }
        }}
      />
    </div>
  );
}

export default EmployeeManagement;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Full CRUD operations (Create, Read, Update, Delete)
  • Data validation for required fields and email format
  • Lookup fields (dropdowns) for department and status
  • Currency formatting for salary
  • Date field for start date
  • Grouping functionality
  • Export capabilities
  • Custom icons
  • Localization for better UX

Common Issues / Troubleshooting

  1. Icons not displaying: Material-Table requires Material-UI icons. Make sure @mui/icons-material is installed and you're providing the icons prop with properly forwarded refs.

  2. Editing not working: Ensure you've implemented the editable prop with onRowAdd, onRowUpdate, and onRowDelete handlers. These should return Promises.

  3. Theme issues: Make sure your app is wrapped with Material-UI's ThemeProvider and CssBaseline component.

  4. TypeScript errors: Install @types/material-table if using TypeScript, or ensure your column and data types match the expected interfaces.

  5. Performance with large datasets: For very large datasets, consider implementing server-side data loading using the data prop with a function that returns a Promise.

Next Steps

Now that you understand Material-Table:

  • Explore advanced features like detail panels and row actions
  • Implement server-side data loading and pagination
  • Add custom cell renderers and formatters
  • Learn about grouping and aggregation
  • Add export functionality (Excel, PDF)
  • Explore theming and customization options
  • Check the official repository: https://github.com/mbrn/material-table
  • Look for part 12 of this series for more advanced topics

Summary

You've learned how to set up Material-Table and create feature-rich data tables with sorting, filtering, pagination, editing, and export capabilities. The library provides comprehensive functionality out of the box, making it easy to build enterprise-grade data management interfaces with Material-UI's design system.

SEO Keywords

material-table React
Material-Table React tutorial
material-table installation
React data table Material-UI
material-table example
React table with editing
material-table CRUD
React data grid Material-UI
material-table setup
React interactive table
material-table filtering
React table component
material-table pagination
React Material-UI table
material-table getting started

Top comments (0)