rx-datatable manual

rx-datatable is a stack of React list/tree/table widgets:

Component Adds Core variant (no wrapper element)
ListBox selection, multiselect, virtualization ListCore
TreeList nesting, expand/collapse TreeListCore
DataTable columns, headings, sorting, resizing DataTableCore

This manual shows the most common usage patterns. For every export, prop and type, see the API reference, which TypeDoc generates from the documentation comments in the source — DataTableProps, DataTableColumn, DataTableCellConfig, TreeListMainProps and ListCoreProps are the ones worth reading in full. The source itself lives at github.com/qwertie/rx-datatable, and also ships inside the npm package under src/.

Installation

npm install rx-datatable
import 'rx-datatable/datatable.css';   // once, anywhere in your app
import { DataTable } from 'rx-datatable';

Import the stylesheet yourself; the package entry point deliberately does not import it for you, because a CSS import there would make the package unloadable by anything without a CSS-aware bundler — plain node, server-side rendering, or your own test runner. Skip it and the tables render unstyled.

All rules in datatable.css use stable class names prefixed dtbl- and read --dtbl-* CSS variables for colors and spacing.

A plain table (single row type)

The only required props are columns and list. By default, a column named n shows row[n], and clicking a heading sorts by that value. Give a column a numeric width to make it user-resizable.

type Planet = { name: string, diameter: number, moons: number };

<DataTable<Planet>
    list={planets}
    selMode="multi"      // Ctrl+click / Shift+click multiselect
    columns={[
        { name: 'name', heading: 'Planet', width: 120 },
        {
            name: 'diameter', heading: 'Diameter', width: 110,
            // getValue/renderCell/RenderCell customize a column at increasing depth
            renderCell: (node, props) => `${(props.value as number).toLocaleString()} km`,
        },
        { name: 'moons', heading: 'Moons', width: 80 },
    ]}/>

Useful column props: getValue (derive the value), getSortableValue and compare (customize sorting; use compare: compareByLocale for text), noSort, noResize, maxWidth, headingProps, cellProps, getColSpan.

A tree grid

Provide getChildren or getParents, and set isTreeCol: true on exactly one column. getParents returns the path of parents (root first) for each item in list; parents that appear in several paths must be distinct values (see getParentPathsOf).

<DataTable<FileInfo|string>
    list={files}
    columns={columns}    // one column has isTreeCol: true
    getParents={f => getParentPathsOf((f as FileInfo).folderPath, '/', true)}
/>

If expansion state or selection seems to reset when the surrounding component re-renders, provide getSelKey so items are identified by a stable key rather than by object identity.

Multiple row types

A table has one set of columns, but each row type can configure how cells get their values and how they render, via getRowConfig. In this example, folders are plain strings while files are FileInfo objects; folder rows span all 3 columns and display only the last path segment:

const folderCells = new Map<string, DataTableCellConfig<string|FileInfo, string>>([
    ['name', {
        getValue: path => path.substring(path.lastIndexOf('/') + 1),
        getColSpan: _ => 3,
    }],
]);

<DataTable<FileInfo|string>
    list={files}
    columns={columns}
    getParents={f => getParentPathsOf((f as FileInfo).folderPath, '/', true)}
    getRowConfig={node => typeof node.item === 'string' ? { columns: folderCells } : undefined}
/>

App-wide configuration: TableConfig

So that individual call sites don't repeat themselves, provide icons and a default theme once near your app root. Nested providers merge with (and override) outer ones.

import { TableConfig, darkTheme } from 'rx-datatable';

<TableConfig
    theme={darkTheme}
    icons={{ sortAscending: <ChevronUp/>, sortDescending: <ChevronDown/>,
             expanded: <ChevronDown/>, collapsed: <ChevronRight/> }}
    renderIconButton={props => <IconButton size="large" {...props}/>}>
    <App/>
</TableConfig>

Defaults: small dependency-free inline SVG chevrons and a plain round-hover <button>.

Theming

A TableTheme is a typed bundle of the --dtbl-* CSS variables, split into two independent halves that combine with an object spread:

Complete themes combining the two are also exported: lightTheme (equals the un-themed default), darkTheme, autoTheme, lightGrayTheme, darkGrayTheme, autoGrayTheme, lightAmberTheme, darkAmberTheme, autoAmberTheme. Variables are applied to the component's outer element — never globally — so differently-themed tables coexist freely:

import { darkTheme, darkColors, compactMetrics, TableTheme } from 'rx-datatable';

const compactDark: TableTheme = { ...darkColors, ...compactMetrics };

<DataTable theme={compactDark} .../>          // per table
<TableConfig theme={darkTheme}>...</TableConfig>   // subtree default

Prefer stylesheets? Pass a string: it becomes a class on the outer element, and your CSS defines the variables:

.compact-blue { --dtbl-cell-padding: 2px 4px; --dtbl-selected-bg: #cde; }

When using the *Core variants (which render no outer element), apply themeProps(myTheme) to your own container element instead.

Reactivity: plain React or MobX

Out of the box (the default adapter), everything works with ordinary React semantics: clicks re-render the table, and you change data by passing new props. Collections you pass for selection, expandedParents or columnsToSortBy can be the package's WatchableSet / WatchableArray, which the components subscribe to, so you can also mutate them from outside the table.

If your app uses MobX, install the MobX adapter once at startup:

import { setReactivityAdapter } from 'rx-datatable';
import { mobxReactivityAdapter } from 'rx-datatable/mobx';  // needs mobx + mobx-react

setReactivityAdapter(mobxReactivityAdapter);

Every component then behaves as a mobx-react observer: rows re-render when observable row data mutates, and selection/expandedParents/columnsToSortBy can be observable.set / observable.array. You can also implement ReactivityAdapter for another library; its five members (observer, runInAction, observableSet, observableArray, computed) are named after the MobX APIs they abstract.

Virtualization

For long lists, pass virtualize={{ height }} (a number, or a function of the item; see VirtualizationSettings). Groups of off-screen rows render as placeholder gaps that materialize as they approach the viewport.

Once loaded, rows do not unload so that the browser's search function (Ctrl+F) can search over any rows that have been viewed. Virtualization isn't designed for scalability; generally rendering is O(N log N) for N rows. Instead, this feature exists because browsers inherently render tables slowly, and we just needed it not to be quite so slow.

<DataTable list={hugeList} columns={columns} virtualize={{ height: 32 }}/>

Odds and ends