Examples › Nested tables with async data loading and sorting
Here is how you can combine asynchronous data loading with sorting in nested tables.
Warning
Nested tables do not work with column pinning - 
pinFirstColumn and pinLastColumn.Click on the column headers and the expandable rows in the table below to see it in action:
| Company / Department / Employee | Employees / Birth date | 
|---|---|
No records
The above example is implemented with the following code:
'use client';
import { Box } from '@mantine/core';
import { IconBuilding, IconChevronRight, IconUser, IconUsers } from '@tabler/icons-react';
import type { DataTableSortStatus } from 'mantine-datatable';
import { DataTable } from 'mantine-datatable';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { useState } from 'react';
import {
  useCompaniesAsync,
  useDepartmentsAsync,
  useEmployeesAsync,
  type CompanyWithEmployeeCount,
} from '~/data/nestedAsync';
import classes from './NestedTablesAsyncSortingExample.module.css';
function EmployeesTable({ departmentId, sortStatus }: { departmentId: string; sortStatus: DataTableSortStatus }) {
  const { records, loading } = useEmployeesAsync({ departmentId, sortStatus });
  return (
    <DataTable
      noHeader
      minHeight={100}
      withColumnBorders
      columns={[
        {
          accessor: 'name',
          noWrap: true,
          render: ({ firstName, lastName }) => (
            <Box component="span" ml={40}>
              <IconUser className={classes.icon} />
              <span>
                {firstName} {lastName}
              </span>
            </Box>
          ),
        },
        {
          accessor: 'birthDate',
          render: ({ birthDate }) => dayjs(birthDate).format('DD MMM YYYY'),
          textAlign: 'right',
          width: 200,
        },
      ]}
      records={records}
      fetching={loading && !records.length}
    />
  );
}
function DepartmentsTable({ companyId, sortStatus }: { companyId: string; sortStatus: DataTableSortStatus }) {
  const { records, loading } = useDepartmentsAsync({ companyId, sortStatus });
  const [expandedRecordIds, setExpandedRecordIds] = useState<string[]>([]);
  return (
    <DataTable
      noHeader
      minHeight={100}
      withColumnBorders
      columns={[
        {
          accessor: 'name',
          noWrap: true,
          render: ({ id, name }) => (
            <Box component="span" ml={20}>
              <IconChevronRight
                className={clsx(classes.icon, classes.expandIcon, {
                  [classes.expandIconRotated]: expandedRecordIds.includes(id),
                })}
              />
              <IconUsers className={classes.icon} />
              <span>{name}</span>
            </Box>
          ),
        },
        { accessor: 'employees', textAlign: 'right', width: 200 },
      ]}
      records={records}
      fetching={loading && !records.length}
      rowExpansion={{
        allowMultiple: true,
        expanded: { recordIds: expandedRecordIds, onRecordIdsChange: setExpandedRecordIds },
        content: ({ record }) => <EmployeesTable departmentId={record.id} sortStatus={sortStatus} />,
      }}
    />
  );
}
export function NestedTablesAsyncSortingExample() {
  const [expandedRecordIds, setExpandedRecordIds] = useState<string[]>([]);
  const [sortStatus, setSortStatus] = useState<DataTableSortStatus<CompanyWithEmployeeCount>>({
    columnAccessor: 'name',
    direction: 'asc',
  });
  const { records, loading } = useCompaniesAsync({ sortStatus });
  return (
    <DataTable
      minHeight={160}
      withTableBorder
      highlightOnHover
      sortStatus={sortStatus}
      onSortStatusChange={setSortStatus}
      withColumnBorders
      columns={[
        {
          accessor: 'name',
          sortable: true,
          title: 'Company / Department / Employee',
          noWrap: true,
          render: ({ id, name }) => (
            <>
              <IconChevronRight
                className={clsx(classes.icon, classes.expandIcon, {
                  [classes.expandIconRotated]: expandedRecordIds.includes(id),
                })}
              />
              <IconBuilding className={classes.icon} />
              <span>{name}</span>
            </>
          ),
        },
        {
          accessor: 'details',
          sortable: true,
          title: 'Employees / Birth date',
          render: ({ employees }) => employees,
          textAlign: 'right',
          width: 200,
        },
      ]}
      records={records}
      fetching={loading}
      rowExpansion={{
        allowMultiple: true,
        expanded: { recordIds: expandedRecordIds, onRecordIdsChange: setExpandedRecordIds },
        content: ({ record }) => (
          <DepartmentsTable companyId={record.id} sortStatus={sortStatus as DataTableSortStatus} />
        ),
      }}
    />
  );
}Head over to the next example to discover more features.











