Skip to main content

Tree data

Tree data represents real hierarchical records rather than value buckets. Real parent rows remain data-bearing, selectable, editable, sortable, and filterable.

Loading interactive example…
React
<Grid
rowIdKey="id"
rowData={rows}
columnDefs={columns}
groupDefaultExpanded={2}
treeData={{
mode: "parent",
getParentId: (row) => row.parentId,
getLabel: (row) => row.name,
columnDef: { label: "Workspace", width: 260 },
}}
/>

Relationship modes​

ModeRow modelSupply
pathclient-sidegetPath(row) returning the complete hierarchy path
parentclient-sideStable IDs plus getParentId(row)
childrenclient-sideRoot objects plus getChildren(row)
serverserver-sidehasChildren(row); the data source answers each parent's children

The three client-side modes support ancestor-preserving filtering, sibling sorting, saved expansion, sticky ancestors, selection, editing, and export. A generated hierarchy column can be configured through treeData.columnDef in every mode.

Sorting is per level: siblings order by their own cell values under the active sort, and each row's children sort among themselves beneath it. A parent's position never depends on its children — ascending, a parent Z with a child A still follows a sibling B. A row with no values of its own to compare (a path ancestor the grid synthesized, or a real ancestor the filters dropped that stays only to reach a matching descendant) sits where its first child would.

Selecting a branch. A parent row is selected like any other row: alone. Set groupSelectsChildren: true to make its checkbox select the parent together with every selectable descendant, collapsed branches included, and clear them all again once all are selected. The parent's checkbox then reflects the branch: ticked when it and all its descendants are selected, a dash when only part of the branch is. Rows the filter removed — including an ancestor kept only to reach a match — and rows isRowSelectable refuses are left out. With mode: "server" only the children the grid has fetched are affected. See Selection → Groups select their children.

Bad relationships in client-side tree data degrade rather than take the grid down, each with one console warning per load (setRowData or transaction) that names the offending ids or paths and how many rows were affected:

  • Duplicate id: the first row with the id is kept and later ones are dropped. In children mode a dropped row's own children stay, under the kept row; the same row object listed twice counts once.
  • Duplicate path (path mode): the first row with the path owns it, and every descendant path hangs under it; each later row is shown as a childless leaf beside it.
  • Missing parent: the row is shown as a root.

A parent cycle still throws. The server-side tree reports its own violations through ServerSideDataError (see below).

A mode belongs to exactly one row model. Pairing a client-side mode with rowModelType: "serverSide" — or mode: "server" with the client-side model — logs a warning and drops the option rather than half-applying it.

// Paths
const pathOptions = {
rowIdKey: "id",
treeData: { mode: "path", getPath: (row) => row.path },
} satisfies GridOptions;
// rows: [{ id: "paris", path: ["World", "Europe", "France", "Paris"] }]

// Parent IDs
const parentOptions = {
rowIdKey: "id",
treeData: {
mode: "parent",
getParentId: (row) => row.parentId,
getLabel: (row) => row.name,
columnDef: { label: "Organization", width: 280 },
},
} satisfies GridOptions;

// Nested children
const childrenOptions = {
rowIdKey: "id",
treeData: {
mode: "children",
getChildren: (row) => row.children,
getLabel: (row) => row.name,
},
} satisfies GridOptions;

Server-side tree data​

mode: "server" moves the hierarchy to the data source: each row declares whether it has children, and the children themselves are requested the first time a row is expanded. Depth is unbounded and siblings are ragged — a parent and a leaf sit side by side — because nothing is derived from column values.

const options = {
rowModelType: "serverSide",
rowIdKey: "id",
serverSideDataSource,
treeData: {
mode: "server",
hasChildren: (row) => row.kind === "folder",
getLabel: (row) => row.name,
// Naming the server's own field lets the server sort and filter the hierarchy column.
columnDef: { label: "Path", key: "name", width: 340 },
},
} satisfies GridOptions;

Requests for a parent's children carry request.treeParent — see server-side data for the wire contract and for refreshing one row's subtree with refreshServerSideData({ rowId }).

Rows stay ordinary data rows: selectable, editable, copyable, and sortable, with the same chevrons, indentation, expansion state, sticky ancestors, and hierarchy keyboard mode as the client-side modes. Three consequences of the server owning the data are worth planning for:

  • Row IDs must be unique across the whole tree, not per parent. Expansion state, the node map, and subtree refreshes all key on getRowId/rowIdKey. A children block that breaks the rule in a way the grid can be certain of — a row repeating one of its own ancestors' IDs, or one response listing an ID twice — is rejected whole and reported through the error event (code row_model_error) with a ServerSideDataError as its details: reason, rowId, parentId, path, and the offending row, so a handler can tell bad data from a failed request with isServerSideDataError(ev.details). The parent stays open over an unloaded slot until the server answers correctly.
  • Filtering is the server's job, ancestor preservation included: a matching descendant is only reachable if the server still returns the parents above it.
  • Export covers the rows the client holds, in the shape on screen: an expanded parent is followed by the children the grid has fetched, a collapsed one stands alone, and nothing is written for blocks never requested. Produce full extracts on the server.

Keyboard navigation​

Tree navigation defaults to ordinary grid behavior. Hierarchy mode maps Ctrl/Cmd+Right to expand and Ctrl/Cmd+Left to collapse or move to the parent. Applications may allow users to switch modes at runtime:

const options = {
treeData: {
mode: "parent",
getParentId: (row) => row.parentId,
keyboardNavigationMode: "hierarchy",
enableKeyboardNavigationModeSwitch: true,
},
} satisfies GridOptions;

api.setKeyboardNavigationMode("grid");
// Both fields are also reconfigurable after mount (the rest of `treeData` is not):
api.setTreeDataKeyboardNavigationOptions({ enableKeyboardNavigationModeSwitch: false });
note

Tree data cannot be combined with value-based row grouping or pivot mode, on either row model.