Mantine DataTable

website·Indexed 30 days ago

https://icflorescu.github.io/mantine-datatable/

A data table component for Mantine featuring support for asynchronous data loading, record selection, expanding rows, searching, filtering, and nested tables. Includes documentation on component properties and various implementation examples for v6.

Tokens
60.6K
Snippets
242
Records
300
Agent score
72%

What's inside Mantine DataTable

  1. Overview of mantine-contextmenu

    mantine-contextmenu is a lightweight, TypeScript-based library for adding desktop-grade context menus to React applications built with Mantine V9.x. It is compatible with modern frameworks like Next.js, Vite, Create React App, Remix, and Gatsby. The library respects Mantine's color scheme (dark/light mode) out of the box and allows for custom content overrides instead of using default generated menu items.
  2. Overview of Mantine ContextMenu for Mantine V6

    Mantine ContextMenu is a lightweight, TypeScript-based library for adding desktop-grade context menus to React applications using Mantine V6. It supports dark mode, is customizable via custom content, and is compatible with frameworks like Next.js, Vite, Create React App, Remix, and Gatsby.
  3. Overview of Mantine DataTable v6

    Mantine DataTable is a lightweight, dependency-free React component designed for data-rich user interfaces. It is specifically built for applications using Mantine V6. Key features include:

    • Asynchronous data loading
    • Pagination and column sorting
    • Multiple row selection (including Gmail-style additive batch selection)
    • Custom cell data rendering
    • Row context menus and row expansion
    • Nested tables
    • Full TypeScript support for type-safe development

    It is compatible with modern React frameworks supported by Mantine, including Next.js, Vite, Create React App, Remix, and Gatsby.

  4. Overview of Mantine DataTable

    Mantine DataTable is a lightweight, dependency-free React component that provides datagrid-like functionality for data-rich user interfaces. It features built-in dark-mode support and Gmail-style additive batch row selection. The library is written in TypeScript, providing well-typed options and JSDoc documentation for type-safe development. It is released under the MIT license and is compatible with Mantine V9.x (with a separate version available for Mantine V6).
  5. Implement client-side searching and filtering in Mantine DataTable

    Mantine DataTable does not provide built-in 'Excel-like' filtering because search and filter logic often depends on specific application architecture (e.g., server-side APIs, global search bars, or complex pagination). Instead, developers should implement filtering by manipulating the records array passed to the component.

    To create a filter UI within the table headers, use the filter property in the column definition. This property accepts a React component (such as TextInput, MultiSelect, or Checkbox) that updates the state used to filter the data records.

    'use client';
    
    import { DataTable } from 'mantine-datatable';
    import { TextInput, MultiSelect, Checkbox } from '@mantine/core';
    import { useDebouncedValue } from '@mantine/hooks';
    import { useEffect, useState } from 'react';
    import dayjs from 'dayjs';
    
    export function SearchingAndFilteringExample() {
      const [records, setRecords] = useState(initialRecords);
      const [query, setQuery] = useState('');
      const [selectedDepartments, setSelectedDepartments] = useState<string[]>([]);
      const [seniors, setSeniors] = useState(false);
      const [debouncedQuery] = useDebouncedValue(query, 200);
    
      // Filter the records based on state changes
      useEffect(() => {
        setRecords(
          initialRecords.filter(({ firstName, lastName, department, birthDate }) => {
            if (debouncedQuery !== '' && !`${firstName} ${lastName}`.toLowerCase().includes(debouncedQuery.trim().toLowerCase()))
              return false;
            if (selectedDepartments.length && !selectedDepartments.some((d) => d === department.name)) 
              return false;
            if (seniors && dayjs().diff(birthDate, 'y') < 70) 
              return false;
            return true;
          })
        );
      }, [debouncedQuery, selectedDepartments, seniors]);
    
      return (
        <DataTable
          records={records}
          columns={[
            {
              accessor: 'name',
              render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
              filter: (
                <TextInput
                  label="Employees"
                  value={query}
                  onChange={(e) => setQuery(e.currentTarget.value)}
                />
              ),
              filtering: query !== '',
            },
            {
              accessor: 'department.name',
              filter: (
                <MultiSelect
                  label="Departments"
                  data={['Engineering', 'Sales', 'HR']}
                  value={selectedDepartments}
                  onChange={setSelectedDepartments}
                />
              ),
              filtering: selectedDepartments.length > 0,
            },
            {
              accessor: 'age',
              filter: () => (
                <Checkbox
                  label="Seniors"
                  checked={seniors}
                  onChange={() => setSeniors((current) => !current)}
                />
              ),
            },
          ]}
        />
      );
    }
  6. Implement a complex data table with asynchronous loading and row expansion

    This example demonstrates how to integrate mantine-datatable with @tanstack/react-query for server-side pagination and sorting, while implementing advanced UI features like row expansion and custom action columns.
    'use client';
    
    import { DataTable, type DataTableColumn, type DataTableProps, type DataTableSortStatus } from 'mantine-datatable';
    import { useQuery } from '@tanstack/react-query';
    import { useState, useCallback } from 'react';
    import { ActionIcon, Group, TextInput, Button } from '@mantine/core';
    import { openModal, closeAllModals } from '@mantine/modals';
    
    const PAGE_SIZE = 100;
    
    export function ComplexUsageExample() {
      const [page, setPage] = useState(1);
      const [sortStatus, setSortStatus] = useState<DataTableSortStatus<Employee>>({
        columnAccessor: 'name',
        direction: 'asc',
      });
      const [selectedRecords, setSelectedRecords] = useState<Employee[]>([]);
    
      // Asynchronous data fetching
      const { data, isFetching } = useQuery({
        queryKey: ['employees', sortStatus.columnAccessor, sortStatus.direction, page],
        queryFn: () => getEmployeesAsync({
          recordsPerPage: PAGE_SIZE,
          page,
          sortStatus,
          delay: { min: 300, max: 500 }
        }),
      });
    
      // Custom column definitions
      const columns: DataTableProps<Employee>['columns'] = [
        {
          accessor: 'name',
          sortable: true,
          render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
        },
        {
          accessor: 'department.company.name',
          title: 'Company',
          sortable: true,
          visibleMediaQuery: '(min-width: 48em)', // Example breakpoint
        },
        {
          accessor: 'actions',
          title: 'Actions',
          width: '0%',
          render: (record) => (
            <Group gap={4} justify="right" wrap="nowrap">
              <ActionIcon
                onClick={(e) => {
                  e.stopPropagation(); // Prevent triggering row click
                  openModal({
                    title: `Message ${record.firstName}`,
                    children: <TextInput placeholder="Your message..." />,
                  });
                }}
              >
                <IconMessage size={16} />
              </ActionIcon>
            </Group>
          ),
        },
      ];
    
      // Row expansion configuration
      const rowExpansion: DataTableProps<Employee>['rowExpansion'] = {
        allowMultiple: true,
        content: ({ record }) => (
          <div style={{ padding: '10px' }}>
            Detailed info for {record.firstName} {record.lastName}
          </div>
        ),
      };
    
      return (
        <DataTable
          columns={columns}
          records={data?.employees}
          totalRecords={data?.total}
          fetching={isFetching}
          page={page}
          onPageChange={setPage}
          recordsPerPage={PAGE_SIZE}
          sortStatus={sortStatus}
          onSortStatusChange={(status) => { setPage(1); setSortStatus(status); }}
          selectedRecords={selectedRecords}
          onSelectedRecordsChange={setSelectedRecords}
          rowExpansion={rowExpansion}
          highlightOnHover
          withTableBorder
          striped
        />
      );
    }
  7. Implement column sorting in Mantine DataTable

    To enable sorting in a Mantine DataTable, you must configure three things: set the sortable property to true on the desired columns, provide a sortStatus object to track the current sorting state, and implement an onSortStatusChange handler to update that state when a header is clicked. It is recommended to disable text selection on the table to prevent accidental text highlighting when users click column headers repeatedly.
    import sortBy from 'lodash/sortBy';
    import { DataTable, DataTableSortStatus } from 'mantine-datatable';
    import { useEffect, useState } from 'react';
    import { companies, type Company } from '~/data';
    
    export default function SortingExample() {
      // 1. Manage sort status state
      const [sortStatus, setSortStatus] = useState<DataTableSortStatus>({
        columnAccessor: 'name',
        direction: 'asc',
      });
    
      const [records, setRecords] = useState(sortBy(companies, 'name'));
    
      // 2. Handle data sorting logic based on sortStatus
      useEffect(() => {
        const data = sortBy(companies, sortStatus.columnAccessor) as Company[];
        setRecords(sortStatus.direction === 'desc' ? data.reverse() : data);
      }, [sortStatus]);
    
      return (
        <DataTable
          withBorder
          withColumnBorders
          records={records}
          columns={[
            { accessor: 'name', width: '40%', sortable: true }, // 3. Mark column as sortable
            { accessor: 'streetAddress', width: '60%' },
            { accessor: 'city', width: 160, sortable: true },
            { accessor: 'state', sortable: true },
          ]}
          sortStatus={sortStatus}
          onSortStatusChange={setSortStatus}
        />
      );
    }
  8. Implement nested tables with asynchronous data loading

    Mantine DataTable's rowExpansion.content function is lazily executed only when a row is expanded. This behavior allows developers to asynchronously load data for nested tables without creating unnecessary DOM elements or triggering API calls for every row on initial load. To implement this, create a separate component for the nested table that fetches its own data based on the ID of the parent record passed via the content function.
    import { DataTable } from 'mantine-datatable';
    
    function ParentTable() {
      const [expandedRecordIds, setExpandedRecordIds] = useState<string[]>([]);
    
      return (
        <DataTable
          records={companies}
          columns={[
            { accessor: 'name', title: 'Company' },
            { accessor: 'employees', title: 'Employees' },
          ]}
          rowExpansion={{
            allowMultiple: true,
            expanded: { recordIds: expandedRecordIds, onRecordIdsChange: setExpandedRecordIds },
            // The content function is lazily executed upon expansion
            content: ({ record }) => <DepartmentsTable companyId={record.id} />,
          }}
        />
      );
    }
    
    function DepartmentsTable({ companyId }: { companyId: string }) {
      // Fetch data asynchronously based on the parent ID
      const { records, loading } = useDepartmentsAsync({ companyId });
    
      return (
        <DataTable
          noHeader
          fetching={loading}
          records={records}
          columns={[
            { accessor: 'name', title: 'Department' },
          ]}
          rowExpansion={{
            allowMultiple: true,
            content: ({ record }) => <EmployeesTable departmentId={record.id} />,
          }}
        />
      );
    }
  9. Add a page size selector to Mantine DataTable

    You can allow users to change the number of records displayed per page by adding a page size selector. This requires providing an array of available sizes and a callback to handle the size change.
    import { DataTable } from 'mantine-datatable';
    import { useState } from 'react';
    
    const PAGE_SIZES = [10, 15, 20];
    
    export default function PageSizeExample() {
      const [pageSize, setPageSize] = useState(PAGE_SIZES[1]);
      const [page, setPage] = useState(1);
    
      return (
        <DataTable
          records={records}
          columns={columns}
          totalRecords={employees.length}
          recordsPerPage={pageSize}
          page={page}
          onPageChange={setPage}
          recordsPerPageOptions={PAGE_SIZES}
          onRecordsPerPageChange={setPageSize}
          recordsPerPageLabel="Records per page"
        />
      );
    }
  10. Contribute code to Mantine DataTable

    To contribute code to Mantine DataTable, you must use pnpm for dependency management and scripts. The project is built with Next.js (App Router) and React Server Components. The package source code is located in the package folder, and the documentation website is in the app folder.

    Key requirements for Pull Requests:

    • Target the next branch (PRs targeting main will be rejected).
    • Include example pages or update existing ones when implementing new features.
    • Ensure code is formatted using the project's tooling.
    # Install dependencies
    pnpm install
    
    # Start development server
    pnpm dev
    
    # Check for linting errors
    pnpm lint
    
    # Format code with Biome
    pnpm format
    
    # Build the project
    pnpm build