Lattice Grid Buy a licence

developer guide

The React Adapter: JavaScript Data Grid Guide

A thin adapter over the same grid every other page runs. You hand it React and createGrid; it hands you a component.

Install

The grid and its adapters are one package. The adapter is a separate entry point, so a bundle that never imports it never carries it.

npm i @toclocoinc/lattice-grid

The adapter is a factory

There is no component to import. You call createLatticeGrid and pass it your copy of React and the grid's createGrid, and it returns a component bound to them.

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

// Build the component once, at module scope, not on every render.
const LatticeGrid = createLatticeGrid({ React, createGrid });

This looks like a hoop, and it is the point. The adapter takes no dependency on React and carries no second copy of the grid: both are passed in, so there is exactly one React and one grid on the page, the versions you installed, and the adapter cannot drift out of step with either. It is a few hundred bytes of glue rather than a bundled framework. Build the component once at module scope; calling the factory on every render would rebuild it each time.

Props flow through the public API

The props are the grid's own configuration. Change rows or columns and the adapter pushes the change through the grid's public API rather than tearing the grid down and rebuilding it, so scroll position, selection and open editors survive a re-render. Events the grid emits arrive as on* callbacks.

function Circuits({ rows }) {
  const columns = [
    { field: 'circuit', title: 'Circuit', layout: { pin: 'start', width: 190 } },
    { field: 'region', title: 'Region', filter: { type: 'set' } },
    { field: 'charge', title: 'Monthly charge', type: 'number',
      format: { style: 'currency', currency: 'GBP' }, total: 'sum' },
  ];

  return (
    <LatticeGrid
      rowKey="id"
      columns={columns}
      rows={rows}
      style={{ height: 520 }}
      onCellChanged={(e) => console.log('changed', e)}
    />
  );
}

Reaching the grid instance

For anything the props do not cover, an export, a programmatic filter, a scroll, the component forwards a ref whose .grid is the real instance. That is the same object the docs describe everywhere else; the adapter adds nothing to it and hides nothing from it.

const gridRef = React.useRef(null);

// gridRef.current.grid is the real grid instance: the whole public API.
<LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} />;

// later, from a button or an effect:
gridRef.current.grid.export.csv({ download: true });

Cleanup on unmount

The adapter destroys the underlying grid when the component unmounts, releasing its listeners and observers, so there is nothing to clean up by hand. Unmount the component and the grid goes with it.

TypeScript

Type declarations travel with the package, so column and config types are picked up without any tsconfig work. Type your columns and the editor checks them against the grid's own definitions.

import type { GridColumn } from '@toclocoinc/lattice-grid';

const columns: GridColumn[] = [
  { field: 'charge', title: 'Charge', type: 'number', total: 'sum' },
];

<LatticeDataGrid>: a drop-in for a Material UI data grid

A React page built against a Material UI data grid does not have to be rewritten prop by prop to move onto Lattice. createLatticeDataGrid from modules/react builds <LatticeDataGrid>, which takes the free-tier DataGrid props under Material UI's own names, so a migrating page keeps its JSX, its column definitions, its controlled models and its callbacks, and changes one import.

// Before: the Material UI DataGrid the page already renders

<DataGrid rows={rows} columns={columns}
  sortModel={sortModel} onSortModelChange={setSortModel}
  filterModel={filterModel} onFilterModelChange={setFilterModel}
  paginationModel={paginationModel} onPaginationModelChange={setPaginationModel}
  checkboxSelection onRowSelectionModelChange={setSelection} />
import { createLatticeDataGrid } from '@toclocoinc/lattice-grid/modules/react';

const LatticeDataGrid = createLatticeDataGrid({ React, ReactDOM, createGrid });

<LatticeDataGrid rows={rows} columns={columns}
  sortModel={sortModel} onSortModelChange={setSortModel}
  filterModel={filterModel} onFilterModelChange={setFilterModel}
  paginationModel={paginationModel} onPaginationModelChange={setPaginationModel}
  checkboxSelection onRowSelectionModelChange={setSelection} />

Every accepted prop maps onto documented grid configuration, an API call or a subscription to a grid event: rows, columns (field, headerName, width, flex, type, valueGetter, valueFormatter, renderCell, editable, sortable, filterable, in MUI's own argument order), getRowId, loading, density, checkboxSelection, disableRowSelectionOnClick, editMode, sortModel/onSortModelChange, filterModel/onFilterModelChange (every free-tier operator), rowSelectionModel/onRowSelectionModelChange, columnVisibilityModel/onColumnVisibilityModelChange, paginationModel/onPaginationModelChange and pageSizeOptions, onRowClick, onCellClick, onCellDoubleClick, onCellEditStop, onRowEditStop, onColumnResize, onColumnOrderChange, onCellModesModelChange, onRowModesModelChange, onDataSourceError, apiRef (getSelectedRows, setSortModel, setFilterModel, scrollToIndexes, exportDataAsCsv, updateRows), slots.toolbar with showToolbar, className and style.

Anything else is refused once, by name, and ignored, never half-applied: one catalogued warning family, react:mui-prop-unsupported:<prop>. Pagination is MUI's own client-side paging over a memory source; hand the component a Lattice source instead, a paged, remote or pushdown one, and paginationModel, onPaginationModelChange and pageSizeOptions are each refused by name, with the rows left as the source delivers them.

// Refused once, by name, and ignored - never half-applied:
// sx, classes, initialState, the server-paging props, dataSource,
// and the Pro/Premium props each carry their own reason,
// as the warning family react:mui-prop-unsupported:<prop>.

<LatticeDataGrid rows={rows} columns={columns}
  source={{ mode: 'paged', pageSize: 100, fetch }}   // a Lattice prop, not an MUI one
  paginationModel={paginationModel}                   // refused: paging is client-side only
  onPaginationModelChange={setPaginationModel}         // refused, same reason
  pageSizeOptions={[25, 50, 100]} />                   // refused, same reason

React-only by design: it exists as a migration path off a Material UI data grid, so the rule that every framework adapter gets every viewer does not extend to it. See design-system presets and theming for themeFromMui, which reads a live MUI theme into the grid's own tokens, and for the Material 3 preset, checked against a real, installed Material UI data grid within a pixel on row height, header height and header weight, so a page moving off <DataGrid> keeps its look as well as its props. It pairs naturally with a page moving over one component at a time.

One grid per page

Use the React adapter or the web component, not both on one page. The web component bundle carries its own copy of the grid, and two copies keep separate registries: a renderer registered through one is invisible to the other. Pick one entry point per page and stay on it.

See also the React data grid overview, and the rest of the developer guide.