DEV Community

Cover image for From v1 to v2.9: how AdaptTable grew into a full React data-grid toolkit
Orwa Mahmoud
Orwa Mahmoud

Posted on

From v1 to v2.9: how AdaptTable grew into a full React data-grid toolkit

Back in July I wrote the first post about AdaptTable.

The project was still v1, and the idea was fairly focused: one table engine that could sit behind the UI kit a React project already uses.

MUI project? Use the MUI adapter.

Mantine? Use Mantine.

Ant Design, Chakra, Radix, shadcn, Base UI, or your own Tailwind styles all sit on the same engine too.

That model is still there. The engine behind it has just grown a lot since then.

The core is at v2.9 now.

GitHub · Live demo · Getting started

The feature set got much bigger

The first release already covered the things I kept needing in application tables: sorting, filtering, pagination, URL state, saved views, column controls, server-side data, mobile layouts, selection, and row/card virtualization.

Since then I've added quite a bit on top of that.

Grouping now supports multiple levels and aggregates.

There is tree data, pivot tables, formula/computed columns, and sparklines.

The editing side grew a lot too.

AdaptTable now has keyboard cell navigation, rectangular range selection, clipboard copy/paste, cut, fill handle behavior, and edit history.

Editing can happen at cell level, row level, or as batched changes. There is validation, async validation, dirty-state tracking, and conflict handling for cases where live data changes while somebody is editing.

Rows have more control now as well:

  • Row reordering
  • Top and bottom row pinning
  • Row and column spanning
  • Full-width rows
  • Custom row heights and styling
  • Tree and nested structures

Export also moved beyond CSV and print, with XLSX and PDF support now available.

Keeping the feature API manageable

As the feature set grew, I wanted a basic table to stay basic.

A normal table can still look like this:

<DataTable
  data={rows}
  columns={columns}
  rowKey={(row) => row.id}
/>
Enter fullscreen mode Exit fullscreen mode

v2 introduced a feature composition API for optional behavior.

For example, row reordering can be added through its own entry point:

import { DataTable } from "@adapttable/mantine";
import { rowReorder } from "@adapttable/mantine/row-reorder";

<DataTable
  data={rows}
  columns={columns}
  rowKey={(row) => row.id}
  features={[
    rowReorder(handleReorder)
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

Grouping, editing, virtualization, cell navigation, saved views, and other features follow the same direction.

There is also a public TableFeature API behind this. A feature can register its own editors, aggregators, commands, filters, menu items, panels, and other extensions through the same feature system.

That gives me a cleaner way to keep expanding AdaptTable without turning every new capability into another permanent special case inside DataTable.

Some of the heavier code is already separate

I also started splitting heavier capabilities into their own entry points instead of exporting everything from the main core package.

Today the core has separate entries for things like:

@adapttable/core/xlsx
@adapttable/core/pdf
@adapttable/core/formula
@adapttable/core/pivot
@adapttable/core/stream
@adapttable/core/sparkline
Enter fullscreen mode Exit fullscreen mode

Those are outside the main core entry, so the application pays the bundle-size cost for those features only when it imports and uses them.

The adapters have feature-specific entry points too. Mantine, for example, exposes paths such as:

@adapttable/mantine/row-reorder
@adapttable/mantine/grouping
@adapttable/mantine/editing
@adapttable/mantine/virtualize
@adapttable/mantine/cell-navigation
Enter fullscreen mode Exit fullscreen mode

v2 still keeps some optional feature implementations reachable from the main DataTable import graph, so the adapter side doesn't get the full bundle-size benefit from those entry points yet.

v3 will move those optional implementations out of the root import graph so applications only pay for the features they actually import and use.

The deprecated v2 APIs will be removed at the same time.

The server side changed a lot too

The original TableSource idea is still there: the table can work with local data or with data controlled by a backend.

That area has expanded quite a bit.

There is now a separate @adapttable/server package for parsing and validating AdaptTable query state on the server.

Pagination, sorting, filters, filter trees, grouping, and pivot state can use the same table query model on both sides instead of needing a second representation just for the backend.

Real-time updates are in now too.

AdaptTable can consume row patches coming through WebSocket or SSE flows instead of reloading the entire dataset whenever something changes.

The live-update work also connects with editing. A remote update can arrive while somebody is changing the same row locally, so those two parts of the table need to understand each other.

There is support for SSR, React Server Components, and streaming as well.

A lot of the work after v1 ended up happening around the data flow and lifecycle of the table, not only in the rendered grid.

Eight adapters, one engine

The public adapters today are:

  • Mantine
  • MUI
  • Chakra UI
  • Ant Design
  • Radix Themes
  • Base UI
  • shadcn/ui
  • unstyled/Tailwind

They all use the same underlying table state and feature logic while rendering with the components and conventions of their own UI stack.

A Mantine project gets Mantine components. An MUI project gets MUI components. A project with its own design system can use the unstyled path.

The core behavior stays shared across them.

Everything is still MIT licensed.

v3 is next

While working on v2, I kept running into one thing I want to fix properly in v3: too much of the engine is still tied to React.

A lot of what AdaptTable does has nothing inherently React-specific about it.

Sorting, filtering, grouping, pivots, formulas, query state, editing state, row models, and feature logic can all exist without React.

For v3 I'm moving that logic into framework-neutral engine modules, with React hooks and components living in a separate binding layer.

React will remain fully supported, but it won't define the architecture of the engine underneath it.

That also opens the door to Vue and Angular integrations later without rebuilding the same table logic for each framework.

v3 will also remove the deprecated v2 APIs and continue the bundle cleanup by moving optional feature implementations out of the root import graph.

I'm also working on optional AI capabilities for v3.

Top comments (0)