The record

Core engineer · QuestionPro · Wick UI · 2024 — present

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

  1. 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.

  2. 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.
Back to the record