Интерактивные таблицы на React: фильтрация, сортировка, группировка

Работа с большими объёмами данных в B2B-приложениях часто упирается в медленные и неудобные таблицы, где фильтрация и сортировка превращаются в ожидание. Мы разрабатываем интерактивные таблицы на React с использованием TanStack Table, реализуя фильтрацию, сортировку и группировку под ключ. Наша команда берёт на себя весь цикл — от проектирования до внедрения и поддержки, обеспечивая стабильную работу даже с тысячами строк.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Интерактивные таблицы на React: фильтрация, сортировка, группировка
Средний
~3-5 дней
Часто задаваемые вопросы

Мы часто сталкиваемся с запросами на создание интерактивных таблиц для B2B-приложений. Основная боль — производительность при фильтрации, сортировке и группировке тысяч строк. Расскажем, как мы решаем эту задачу с помощью TanStack Table — headless-библиотеки, которая даёт полный контроль над UI и поведением. За 5 лет опыта мы реализовали более 30 проектов с таблицами данных, и TanStack Table — наш выбор по умолчанию. Один из проектов — CRM-система с таблицей заказов на 50 000 строк. Клиентская сортировка приводила к фризам на 10 секунд. Мы предложили server-side обработку — проблема решилась.

Как реализовать интерактивные таблицы с фильтрацией, сортировкой и группировкой?

TanStack Table v8 — современное решение для React. Установка: npm install @tanstack/react-table. Основные концепции: useReactTable принимает конфигурацию с моделями (getCoreRowModel, getSortedRowModel, getFilteredRowModel, getGroupedRowModel, getExpandedRowModel, getPaginationRowModel). Это позволяет быстро добавить функциональность без boilerplate. Для больших таблиц используем react-virtual для виртуализации строк — это снижает нагрузку на DOM.

Пошаговая инструкция:

  1. Определите интерфейс данных, например Order.
  2. Создайте колонки через createColumnHelper.
  3. Настройте состояние: sorting, columnFilters, grouping, pagination.
  4. Подключите модели строк.
  5. Рендерите таблицу: хедеры с сортировкой, фильтры для колонок, тело с группировкой и агрегацией, пагинацию.

Пример: Таблица заказов с группировкой и фильтрацией — интерактивные таблицы на

import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getGroupedRowModel, getExpandedRowModel, getPaginationRowModel, flexRender, createColumnHelper } from '@tanstack/react-table';

interface Order {
  id: string;
  customerName: string;
  status: OrderStatus;
  total: number;
  category: string;
  createdAt: Date;
}

const columnHelper = createColumnHelper<Order>();

const columns = [
  columnHelper.accessor('id', {
    header: '№',
    size: 80,
    cell: ({ getValue }) => <code className="text-xs">{getValue().slice(0, 8)}</code>
  }),
  columnHelper.accessor('customerName', {
    header: 'Покупатель',
    filterFn: 'includesString'
  }),
  columnHelper.accessor('status', {
    header: 'Статус',
    cell: ({ getValue }) => <StatusBadge status={getValue()} />,
    filterFn: (row, columnId, filterValue) => filterValue.includes(row.getValue(columnId))
  }),
  columnHelper.accessor('total', {
    header: 'Сумма',
    cell: ({ getValue }) => formatCurrency(getValue()),
    sortDescFirst: true,
    aggregationFn: 'sum',
    aggregatedCell: ({ getValue }) => (
      <strong className="font-semibold">{formatCurrency(getValue<number>())}</strong>
    )
  }),
  columnHelper.accessor('category', {
    header: 'Категория'
  }),
  columnHelper.accessor('createdAt', {
    header: 'Дата',
    cell: ({ getValue }) => format(getValue(), 'dd.MM.yyyy HH:mm'),
    sortingFn: 'datetime'
  })
];

function OrdersTable({ data }) {
  const [sorting, setSorting] = useState([{ id: 'createdAt', desc: true }]);
  const [columnFilters, setColumnFilters] = useState([]);
  const [globalFilter, setGlobalFilter] = useState('');
  const [grouping, setGrouping] = useState<string[]>([]);
  const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 });

  const table = useReactTable({
    data,
    columns,
    state: {
      sorting,
      columnFilters,
      globalFilter,
      grouping,
      pagination
    },
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    onGlobalFilterChange: setGlobalFilter,
    onGroupingChange: setGrouping,
    onPaginationChange: setPagination,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getGroupedRowModel: getGroupedRowModel(),
    getExpandedRowModel: getExpandedRowModel(),
    getPaginationRowModel: getPaginationRowModel()
  });

  return (
    <div>
      {/* Глобальный поиск */}
      <input
        value={globalFilter}
        onChange={e => setGlobalFilter(e.target.value)}
        placeholder="Поиск по всем полям..."
        className="mb-4 px-3 py-2 border rounded-lg w-64"
      />

      {/* Группировка */}
      <div className="flex gap-2 mb-4">
        <span className="text-sm text-gray-600">Группировка:</span>
        {['status', 'category'].map(col => (
          <button
            key={col}
            onClick={() => setGrouping(prev =>
              prev.includes(col) ? prev.filter(c => c !== col) : [...prev, col]
            )}
            className={`text-xs px-2 py-1 rounded border ${
              grouping.includes(col) ? 'bg-blue-50 border-blue-300' : 'border-gray-200'
            }`}
          >
            {col}
          </button>
        ))}
      </div>

      {/* Таблица */}
      <div className="overflow-x-auto rounded-lg border border-gray-200">
        <table className="w-full text-sm">
          <thead className="bg-gray-50">
            {table.getHeaderGroups().map(headerGroup => (
              <tr key={headerGroup.id}>
                {headerGroup.headers.map(header => (
                  <th
                    key={header.id}
                    className="px-4 py-3 text-left font-medium text-gray-600 whitespace-nowrap"
                    style={{ width: header.getSize() }}
                  >
                    <div className="flex items-center gap-1">
                      <span
                        className={header.column.getCanSort() ? 'cursor-pointer select-none' : ''}
                        onClick={header.column.getToggleSortingHandler()}
                      >
                        {flexRender(header.column.columnDef.header, header.getContext())}
                      </span>
                      {header.column.getIsSorted() === 'asc' && ' ↑'}
                      {header.column.getIsSorted() === 'desc' && ' ↓'}
                    </div>
                    {/* Фильтр для каждой колонки */}
                    {header.column.getCanFilter() && (
                      <ColumnFilter column={header.column} />
                    )}
                  </th>
                ))}
              </tr>
            ))}
          </thead>
          <tbody className="divide-y divide-gray-100">
            {table.getRowModel().rows.map(row => (
              <tr
                key={row.id}
                className={`hover:bg-gray-50 ${row.getIsGrouped() ? 'bg-gray-50 font-medium' : ''}`}
              >
                {row.getVisibleCells().map(cell => (
                  <td key={cell.id} className="px-4 py-3">
                    {cell.getIsGrouped() ? (
                      <button
                        onClick={row.getToggleExpandedHandler()}
                        className="flex items-center gap-1"
                      >
                        {row.getIsExpanded() ? '▼' : '▶'}
                        {flexRender(cell.column.columnDef.cell, cell.getContext())}
                        ({row.subRows.length})
                      </button>
                    ) : cell.getIsAggregated() ? (
                      flexRender(cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell, cell.getContext())
                    ) : flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Пагинация */}
      <TablePagination table={table} />
    </div>
  );
}

Почему стоит выбрать server-side обработку для больших таблиц?

При работе с более чем 10 000 строк клиентская обработка становится медленной — LCP растёт, пользователи испытывают фризы. Server-side сортировка и фильтрация решают проблему: данные загружаются порциями, а сортировка выполняется на сервере. TanStack Table documentation: "Headless UI library for building powerful tables and datagrids." Пример реализации:

function ServerSideTable() {
  const [sorting, setSorting] = useState([]);
  const [columnFilters, setColumnFilters] = useState([]);
  const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 });
  const { data, isLoading } = useQuery({
    queryKey: ['orders', { sorting, columnFilters, pagination }],
    queryFn: () => fetch('/api/orders?' + new URLSearchParams({
      sort: sorting.map(s => `${s.id}:${s.desc ? 'desc' : 'asc'}`).join(','),
      filters: JSON.stringify(columnFilters),
      page: String(pagination.pageIndex + 1),
      per_page: String(pagination.pageSize)
    })).then(r => r.json())
  });
  const table = useReactTable({
    data: data?.items ?? [],
    rowCount: data?.total ?? 0,
    manualSorting: true,
    manualFiltering: true,
    manualPagination: true,
    // ...
  });
}

Сравнение подходов: client-side vs server-side

Критерий Client-side Server-side
Количество строк до 10 000 от 10 000
Скорость сортировки мгновенно с задержкой сети
Фильтрация все строки запрос к API
Группировка на клиенте на сервере
Нагрузка на браузер высокая низкая

Для таблиц с объёмом данных до 1 000 строк client-side обычно быстрее, поскольку не требует сетевых запросов. При росте данных server-side становится единственно возможным вариантом — он в 5-10 раз снижает время отклика интерфейса.

Детальный анализ производительности: при 100 000 строк client-side рендеринг может занимать более 5 секунд, в то время как server-side с пагинацией по 50 строк загружается за 0.2 сек. TanStack Table с virtualized rows (через react-virtual) позволяет рендерить только видимые строки, что ещё больше ускоряет интерфейс. В отличие от коммерческих решений, TanStack Table — бесплатная open-source библиотека, что существенно экономит бюджет проекта.

Пример конфигурации агрегации для группировки

Для корректного отображения сумм в сгруппированных строках нужно задать aggregationFn для колонки. Например, для колонки total используем aggregationFn: 'sum'. Можно также определить кастомную функцию.

Какие преимущества даёт группировка строк в интерактивной таблице?

Группировка позволяет объединять строки по значению колонки, например, по статусу или категории. Это упрощает анализ данных: сразу видно количество записей в каждой группе и агрегированные значения (сумма, среднее и т.д.). В TanStack Table группировка реализуется через getGroupedRowModel и getExpandedRowModel, а пользователь может сворачивать и разворачивать группы. Группировка эффективна при работе с иерархическими данными и часто используется в системах аналитики.

Типичные ошибки при разработке таблиц с фильтрацией и сортировкой

  • Игнорирование виртуализации: без react-virtual даже 5 000 строк могут вызвать задержки. Всегда подключайте @tanstack/react-virtual.
  • Отсутствие debounce при фильтрации: каждый ввод символа вызывает перерисовку. Используйте useDebounce с задержкой 300 мс.
  • Неправильная настройка колонок: size и minSize должны быть заданы, иначе таблица может разъезжаться.
  • Забытая агрегация при группировке: если не задать aggregationFn, сгруппированные строки покажут undefined.

Что входит в разработку интерактивной таблицы под ключ?

  • Аналитика: определение набора колонок, типов данных, требований к производительности.
  • Проектирование: выбор архитектуры (client-side/server-side), UI-компонентов.
  • Реализация: настройка сортировки, фильтрации, группировки, пагинации, экспорта, кастомных фильтров (date range, multiselect).
  • Тестирование: проверка на больших объёмах данных, Edge-кейсы, оптимизация Core Web Vitals.
  • Деплой и документация: размещение на сервере, описание API и инструкция для разработчиков.
  • Обучение: передача знаний команде заказчика.

Мы гарантируем, что таблица будет работать с Core Web Vitals в норме (LCP < 2.5s, CLS < 0.1). Получите консультацию по вашему проекту — свяжитесь с нами.

Типовые сроки и этапы

Этап Длительность
Аналитика 1-2 дня
Проектирование и прототип 2-3 дня
Реализация core-функций 5-7 дней
Интеграция с API (server-side) 3-5 дней
Тестирование и оптимизация 2-3 дня

Сроки разработки

  • Базовая таблица (сортировка, фильтры, пагинация, группировка): от 5 до 7 дней.
  • Добавление server-side, экспорта в Excel/CSV, кастомных виджетов: ещё от 5 до 7 дней.

Стоимость рассчитывается индивидуально, зависит от сложности и объёма данных. Обращайтесь — подберём оптимальное решение.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1543
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1374
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060