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.