The Data Grid — performance under real interaction
Built the system's most complex component so it stays smooth on large datasets while supporting every interaction product teams asked for.
- 1
- Generalised API, many products
- Large
- Datasets, kept smooth
React · TypeScript · TanStack Table · react-virtuoso · CSS Modules
The Data Grid is the most complex component in Wick UI — the place where performance, accessibility, and a general-purpose API all have to hold at once.
Context
Data-dense product surfaces — survey results, admin tables, analytics — all needed the same powerful table, but each had been approximating it with one-off grids that buckled as rows grew.
Problem
A single grid had to support virtualization, sticky columns, row selection, expandable rows, inline editing, pagination, and bulk actions — and stay responsive on large datasets — while exposing one API general enough for every product to adopt.
Constraints
- It had to stay smooth as row counts grew well beyond what the DOM can hold at once.
- Every interaction mode (selection, expansion, inline edit, bulk action) had to compose, not fight each other.
- As part of a shared system, the API had to generalise rather than encode any one product's quirks.
Decisions
-
Compose TanStack Table (headless) with react-virtuoso for virtualization.
Why Headless table logic keeps state and column models explicit; virtualization keeps the DOM small regardless of dataset size.
-
Keep selection, expansion, and editing as controlled, explicit state.
Why Predictable state is what lets the interaction modes compose instead of producing surprises when combined.
Trade-offs
-
Virtualization and a general column model over A simpler grid that renders every row
The component's whole reason to exist was staying smooth at scale; simplicity that breaks under load isn't simpler.
Results
- A single Data Grid that stays smooth on large datasets across multiple product surfaces.
- Sticky columns, row selection, expandable rows, inline editing, pagination, and bulk actions in one composable component.
Lessons
- Measure before optimising — the perceived "slow render" was often unnecessary re-renders, not the row count.
- For a shared component, the hard problem is the API surface, not the rendering. Get the model right and the features compose.