DEV Community

Nathan Collins
Nathan Collins

Posted on

Advanced Data Table Implementation with mui-datatables in React

mui-datatables is a powerful, feature-rich data table component for React built on Material-UI that provides comprehensive functionality including sorting, filtering, pagination, search, export, and custom rendering. It offers extensive customization options and advanced features for building complex data management interfaces. This guide walks through implementing advanced table features using mui-datatables with React, covering custom components, server-side processing, and enterprise-grade implementations. This is part 12 of a series on using mui-datatables 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 (recommended)
  • Knowledge of async operations and API integration

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 mui-datatables:

npm install mui-datatables
Enter fullscreen mode Exit fullscreen mode

Or install everything with yarn:

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

Or with pnpm:

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

Your package.json should include:

{
  "dependencies": {
    "mui-datatables": "^4.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

mui-datatables 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 from 'react';
import MUIDataTable from 'mui-datatables';

function DataTable() {
  const columns = [
    { name: 'id', label: 'ID', options: { filter: false } },
    { name: 'name', label: 'Name' },
    { name: 'email', label: 'Email' },
    { name: 'age', label: 'Age', options: { filter: false } },
    { name: 'city', label: 'City' }
  ];

  const data = [
    { 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 options = {
    filterType: 'checkbox',
    responsive: 'standard',
    selectableRows: 'multiple',
    print: false,
    download: true,
    search: true,
    pagination: true
  };

  return (
    <div style={{ padding: '20px' }}>
      <MUIDataTable
        title="Employee Directory"
        data={data}
        columns={columns}
        options={options}
      />
    </div>
  );
}

export default DataTable;
Enter fullscreen mode Exit fullscreen mode

Understanding the Basics

mui-datatables uses a column-based configuration where:

  • name: Property key in your data objects
  • label: Column header text
  • options: Column-specific options (filtering, sorting, custom rendering)

Key concepts for advanced usage:

  • Custom Renderers: Use customBodyRender for custom cell content
  • Custom Filters: Implement custom filter components
  • Server-side Processing: Load data asynchronously from APIs
  • Options: Extensive configuration options for table behavior
  • Events: Handle row selection, cell clicks, and other interactions

Here's an example with custom rendering and filters:

// src/AdvancedTable.jsx
import React, { useState } from 'react';
import MUIDataTable from 'mui-datatables';
import { Chip, IconButton } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';

function AdvancedTable() {
  const [data, setData] = useState([
    { 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 = [
    { 
      name: 'id', 
      label: 'ID',
      options: { filter: false, sort: true }
    },
    { 
      name: 'name', 
      label: 'Product Name'
    },
    { 
      name: 'category', 
      label: 'Category',
      options: {
        filter: true,
        filterType: 'dropdown'
      }
    },
    { 
      name: 'price', 
      label: 'Price',
      options: {
        customBodyRender: (value) => `$${value.toFixed(2)}`,
        filter: false
      }
    },
    { 
      name: 'stock', 
      label: 'Stock',
      options: {
        customBodyRender: (value) => (
          <span style={{ 
            color: value < 10 ? 'red' : 'green',
            fontWeight: 'bold'
          }}>
            {value}
          </span>
        ),
        filter: false
      }
    },
    { 
      name: 'status', 
      label: 'Status',
      options: {
        customBodyRender: (value) => (
          <Chip 
            label={value} 
            color={value === 'In Stock' ? 'success' : 'warning'}
            size="small"
          />
        ),
        filter: true,
        filterType: 'dropdown'
      }
    },
    {
      name: 'actions',
      label: 'Actions',
      options: {
        filter: false,
        sort: false,
        customBodyRender: (value, tableMeta) => {
          const rowIndex = tableMeta.rowIndex;
          return (
            <>
              <IconButton 
              size="small" 
              onClick={() => handleEdit(rowIndex)}
              color="primary"
            >
              <EditIcon />
            </IconButton>
            <IconButton 
              size="small" 
              onClick={() => handleDelete(rowIndex)}
              color="error"
            >
              <DeleteIcon />
            </IconButton>
            </>
          );
        }
      }
    }
  ];

  const handleEdit = (rowIndex) => {
    console.log('Edit row:', data[rowIndex]);
    // Implement edit logic
  };

  const handleDelete = (rowIndex) => {
    const newData = data.filter((_, index) => index !== rowIndex);
    setData(newData);
  };

  const options = {
    filterType: 'multiselect',
    responsive: 'standard',
    selectableRows: 'multiple',
    selectableRowsOnClick: true,
    rowsPerPage: 10,
    rowsPerPageOptions: [5, 10, 20, 50],
    onRowsDelete: (rowsDeleted) => {
      const indices = rowsDeleted.data.map(d => d.dataIndex);
      setData(data.filter((_, index) => !indices.includes(index)));
      return false; // Prevent default deletion
    },
    customToolbar: () => {
      return (
        <button 
          onClick={() => console.log('Custom action')}
          style={{ marginRight: '10px', padding: '8px 16px' }}
        >
          Custom Action
        </button>
      );
    }
  };

  return (
    <div style={{ padding: '20px' }}>
      <MUIDataTable
        title="Product Inventory"
        data={data}
        columns={columns}
        options={options}
      />
    </div>
  );
}

export default AdvancedTable;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive admin dashboard with server-side processing:

// src/AdminDashboard.jsx
import React, { useState, useEffect, useCallback } from 'react';
import MUIDataTable from 'mui-datatables';
import { Chip, IconButton, Button, Box } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import AddIcon from '@mui/icons-material/Add';

function AdminDashboard() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [totalCount, setTotalCount] = useState(0);

  const fetchData = useCallback(async (page, pageSize, searchText, sortOrder) => {
    setLoading(true);
    try {
      // Simulate API call
      const params = new URLSearchParams({
        page: page.toString(),
        limit: pageSize.toString(),
        search: searchText || '',
        sortBy: sortOrder?.name || '',
        sortOrder: sortOrder?.direction || 'asc'
      });

      const response = await fetch(`/api/users?${params}`);
      const result = await response.json();

      setData(result.data);
      setTotalCount(result.total);
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchData(0, 10, '', null);
  }, [fetchData]);

  const columns = [
    { 
      name: 'id', 
      label: 'ID',
      options: { 
        filter: false,
        sort: true,
        display: true
      }
    },
    { 
      name: 'avatar', 
      label: 'Avatar',
      options: {
        filter: false,
        sort: false,
        customBodyRender: (value) => (
          <img 
            src={value || '/default-avatar.png'} 
            alt="Avatar"
            style={{
              width: '40px',
              height: '40px',
              borderRadius: '50%',
              objectFit: 'cover'
            }}
          />
        )
      }
    },
    { 
      name: 'name', 
      label: 'Full Name',
      options: {
        filter: true,
        customBodyRender: (value, tableMeta) => {
          const row = data[tableMeta.rowIndex];
          return (
            <div>
              <strong>{value}</strong>
              <div style={{ fontSize: '12px', color: '#666' }}>
                {row.email}
              </div>
            </div>
          );
        }
      }
    },
    { 
      name: 'department', 
      label: 'Department',
      options: {
        filter: true,
        filterType: 'multiselect',
        customBodyRender: (value) => {
          const colors = {
            Engineering: '#dc3545',
            Marketing: '#ffc107',
            Sales: '#28a745',
            HR: '#17a2b8'
          };
          return (
            <Chip
              label={value}
              size="small"
              style={{
                backgroundColor: colors[value] || '#6c757d',
                color: 'white'
              }}
            />
          );
        }
      }
    },
    { 
      name: 'salary', 
      label: 'Salary',
      options: {
        filter: false,
        sort: true,
        customBodyRender: (value) => `$${value.toLocaleString()}`,
        customSort: (data1, data2) => {
          return data1[4] - data2[4];
        }
      }
    },
    { 
      name: 'status', 
      label: 'Status',
      options: {
        filter: true,
        filterType: 'dropdown',
        customBodyRender: (value) => (
          <Chip
            label={value}
            color={value === 'Active' ? 'success' : 'error'}
            size="small"
          />
        )
      }
    },
    { 
      name: 'lastLogin', 
      label: 'Last Login',
      options: {
        filter: false,
        customBodyRender: (value) => {
          if (!value) return 'Never';
          const date = new Date(value);
          return date.toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'short',
            day: 'numeric'
          });
        }
      }
    },
    {
      name: 'actions',
      label: 'Actions',
      options: {
        filter: false,
        sort: false,
        customBodyRender: (value, tableMeta) => {
          const rowIndex = tableMeta.rowIndex;
          return (
            <>
              <IconButton 
                size="small" 
                onClick={() => handleEdit(rowIndex)}
                color="primary"
              >
                <EditIcon />
              </IconButton>
              <IconButton 
                size="small" 
                onClick={() => handleDelete(rowIndex)}
                color="error"
              >
                <DeleteIcon />
              </IconButton>
            </>
          );
        }
      }
    }
  ];

  const handleEdit = (rowIndex) => {
    console.log('Edit user:', data[rowIndex]);
    // Implement edit logic
  };

  const handleDelete = async (rowIndex) => {
    if (window.confirm('Are you sure you want to delete this user?')) {
      try {
        await fetch(`/api/users/${data[rowIndex].id}`, { method: 'DELETE' });
        fetchData(0, 10, '', null);
      } catch (error) {
        console.error('Delete failed:', error);
      }
    }
  };

  const options = {
    serverSide: true,
    count: totalCount,
    page: 0,
    rowsPerPage: 10,
    rowsPerPageOptions: [5, 10, 20, 50],
    filterType: 'multiselect',
    responsive: 'standard',
    selectableRows: 'multiple',
    search: true,
    download: true,
    print: false,
    onTableChange: (action, tableState) => {
      switch (action) {
        case 'changePage':
          fetchData(tableState.page, tableState.rowsPerPage, tableState.searchText, tableState.sortOrder);
          break;
        case 'changeRowsPerPage':
          fetchData(0, tableState.rowsPerPage, tableState.searchText, tableState.sortOrder);
          break;
        case 'search':
          fetchData(tableState.page, tableState.rowsPerPage, tableState.searchText, tableState.sortOrder);
          break;
        case 'sort':
          fetchData(tableState.page, tableState.rowsPerPage, tableState.searchText, tableState.sortOrder);
          break;
        case 'filterChange':
          fetchData(tableState.page, tableState.rowsPerPage, tableState.searchText, tableState.sortOrder);
          break;
        default:
          break;
      }
    },
    customToolbar: () => {
      return (
        <Button
          variant="contained"
          color="primary"
          startIcon={<AddIcon />}
          onClick={() => console.log('Add new user')}
          style={{ marginRight: '10px' }}
        >
          Add User
        </Button>
      );
    },
    onRowsDelete: (rowsDeleted) => {
      const indices = rowsDeleted.data.map(d => d.dataIndex);
      const idsToDelete = indices.map(i => data[i].id);

      // Delete multiple rows
      Promise.all(
        idsToDelete.map(id => 
          fetch(`/api/users/${id}`, { method: 'DELETE' })
        )
      ).then(() => {
        fetchData(0, 10, '', null);
      });

      return false;
    }
  };

  return (
    <Box sx={{ p: 3 }}>
      <MUIDataTable
        title="Admin Dashboard"
        data={data}
        columns={columns}
        options={options}
      />
    </Box>
  );
}

export default AdminDashboard;
Enter fullscreen mode Exit fullscreen mode

This advanced example demonstrates:

  • Server-side data loading with pagination, sorting, and filtering
  • Custom cell rendering with React components (Chip, IconButton, images)
  • Custom filters with multiselect and dropdown options
  • Row selection and bulk deletion
  • Custom toolbar with action buttons
  • Real-time data updates
  • Loading states
  • Custom sorting logic
  • Event handling for all table interactions

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 your data array and columns array are properly structured.

  2. Server-side processing not working: Make sure you've set serverSide: true in options and implemented the onTableChange handler to fetch data based on table state changes.

  3. Custom rendering not working: Verify that you're using customBodyRender correctly in column options. The function receives (value, tableMeta) parameters.

  4. Filtering issues: Ensure you've set appropriate filterType in column options ('checkbox', 'dropdown', 'multiselect', etc.). Some filter types require specific data formats.

  5. Performance issues: For large datasets, always use server-side processing. Client-side processing can be slow with thousands of rows.

  6. TypeScript errors: Install type definitions if available. mui-datatables has TypeScript support, so ensure your column and data types match the expected interfaces.

Next Steps

Now that you've mastered mui-datatables:

  • Explore advanced customization options and themes
  • Implement custom filter components
  • Add export functionality (CSV, Excel, PDF)
  • Learn about column resizing and reordering
  • Implement row expansion for nested data
  • Add custom footer and header components
  • Check the official repository: https://github.com/gregnb/mui-datatables
  • Look for part 13 of this series for more advanced topics

Summary

You've learned how to implement advanced data table features with mui-datatables, including server-side processing, custom rendering, filtering, sorting, and complex interactions. The library provides extensive functionality for building enterprise-grade data management interfaces with Material-UI's design system and excellent performance.

SEO Keywords

mui-datatables React
mui-datatables tutorial
React advanced data table
mui-datatables installation
React Material-UI table
mui-datatables server-side
React data grid advanced
mui-datatables custom rendering
React table component
mui-datatables setup
React interactive table
mui-datatables filtering
React enterprise table
mui-datatables pagination
React Material-UI data table

Top comments (0)