Lattice Grid Buy a licence

react data grid · row grouping

React Grid: Row Grouping

Nest rows by account, then by service inside it, and every group carries its own subtotal. Grouping, sorting and filtering compose: a filter narrows the rows inside every group rather than replacing the grouping.

Also in: Angular

Install and import

npm install @toclocoinc/lattice-grid
# free on localhost; a production domain needs a licence key (see /pricing/)

The files

lattice.ts

// lattice.ts
import React from 'react';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';

export const LatticeGrid = createLatticeGrid({ React, createGrid });

CloudCosts.tsx

// CloudCosts.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { LatticeGrid } from './lattice';

// group nests by account, then by service inside it, in the order given by
// index. showTotalInHeader puts each group's own reduction on its heading
// row, so the "Monthly cost" heading names its group's total, not just the
// column.
const columns = [
  { field: 'account', title: 'Account', filter: { type: 'set' }, group: { enabled: true, index: 0 } },
  { field: 'service', title: 'Service', filter: { type: 'set' }, group: { enabled: true, index: 1 } },
  { field: 'resource', title: 'Resource', layout: { flex: 1, min: 200, max: 320 } },
  { field: 'environment', title: 'Env', filter: { type: 'set' },
    cell: { decoration: 'pill', variant: { map: {
      prod: 'danger', 'prod-2': 'danger', 'definitely-prod': 'danger',
      staging: 'warning', untagged: 'warning', test: 'info',
      dev: 'success', 'not-prod': 'neutral',
    } } } },
  { field: 'cost', title: 'Monthly cost', type: 'number', layout: { width: 170 },
    format: { style: 'currency', currency: 'USD', decimals: 2 }, total: 'sum' },
];

const rows = [/* 10,000 cloud cost records */];

function CloudCosts() {
  return (
    <LatticeGrid
      rowKey="id"
      selection="multiple"
      showTotalInHeader
      toolPanel={{ side: 'left', panels: ['columns', 'filters', 'views', 'quick'],
        actions: ['undo', 'redo', 'export', 'restore', 'maximise'], exportName: 'lattice-demo' }}
      columns={columns}
      rows={rows}
      style={{ height: '480px' }}
    />
  );
}

createRoot(document.getElementById('cloud-costs')!).render(<CloudCosts />);

See it running

Building…
Loading a live grid…

Open this demo's React tab →

Working in React

Grouping is column configuration, not extra markup. There is no separate "group row" component to render: group.index on a column is the whole configuration, and the grid draws the nested headers and footers itself.

columns is mount-time. Like every array prop here, hoist it outside the component or memoise it; an inline array literal is a new reference on every render and reconfigures grouping on every render along with it.

Group state survives a re-render. Expanding or collapsing a group is grid state, not React state, so a parent re-rendering for an unrelated reason does not collapse what a reader already opened.

Related