React Tree Data Grid
React applications can start with a free/core tree table by deriving a flat display list in state and rendering indentation through a React cell template.
Tree-like rows do not require Pro when your application only needs a visual hierarchy. The free/core approach is to flatten parent-child records into display order, add a level field, and render indentation in a cell template.
This keeps RevoGrid simple: the grid receives a normal source array, virtualization still works, and your application owns the hierarchy transformation.
Tree model
Start with records that have an ID and a parent ID:
const inputData = [
{ id: 4, parentId: 0, name: 'Antoni father for 2' },
{ id: 5, parentId: 4, name: 'Odin' },
{ id: 6, parentId: 4, name: 'John' },
{ id: 1, parentId: 0, name: 'Mary mother for 2' },
{ id: 2, parentId: 1, name: 'Howard has also 2' },
];Convert that data into a flat display list before assigning it to the grid:
function buildTreeData(rows: typeof inputData, rootParentId = 0) {
const childrenByParent = new Map<number, typeof inputData>();
for (const row of rows) {
const children = childrenByParent.get(row.parentId) ?? [];
children.push(row);
childrenByParent.set(row.parentId, children);
}
const result: Array<(typeof inputData)[number] & { level: number }> = [];
function appendChildren(parentId: number, level = 0) {
for (const child of childrenByParent.get(parentId) ?? []) {
result.push({ ...child, level });
appendChildren(child.id, level + 1);
}
}
appendChildren(rootParentId);
return result;
}Indented cell template
The core demo uses a normal cell template to show hierarchy depth:
const columns = [
{
prop: 'name',
name: 'Tree',
size: 300,
cellTemplate(h, { value, model }) {
return h(
'div',
{
style: {
marginLeft: `${(model?.level ?? 0) * 30}px`,
},
},
value || '',
);
},
},
{
prop: 'level',
readonly: true,
},
];Use the core approach when the hierarchy is read-only or mostly decorative: category outlines, simple nested lists, document sections, or a tree table where your app handles all expand/collapse logic outside the grid.
Core limits
The free/core pattern is intentionally lightweight. It does not add built-in expand/collapse controls, automatic parent-child visibility, tree-aware drag-and-drop, sticky parent rows, or parent/descendant row selection behavior. Those behaviors belong in the Pro Tree Data plugin.
React notes
- Use
useMemofor columns and derived tree rows when input data is stable. - Use a React cell template for indentation in the core version.
- Keep expand/collapse state in React if you add your own free tree controls.
<RevoGrid
columns={columns}
source={treeData}
readonly
canFocus={false}
hideAttribution
/>Pro Tree Data
RevoGrid Pro adds a dedicated TreeDataPlugin for advanced data grid tree structures. It keeps the original source flat, computes hierarchy metadata internally, and renders tree controls in the column marked with tree: true.
RevoGrid Pro
Use the Pro plugin when hierarchy is not just indentation: users need expand/collapse controls, persistent expansion state, filtering and sorting with hierarchy awareness, row selection across descendants, drag-and-drop reparenting, or sticky parent rows while scrolling long branches.
Pro data model
The Pro model also starts from stable IDs and parent IDs:
type TeamRow = {
id: string;
parentId: string | null;
name: string;
role: string;
budget: number;
};
const source: TeamRow[] = [
{ id: 'eng', parentId: null, name: 'Engineering', role: 'Department', budget: 850000 },
{ id: 'platform', parentId: 'eng', name: 'Platform', role: 'Team', budget: 320000 },
{ id: 'api', parentId: 'platform', name: 'API', role: 'Squad', budget: 140000 },
{ id: 'design', parentId: null, name: 'Design', role: 'Department', budget: 260000 },
];Unlike the core example, you do not add level yourself. The plugin derives level, visibility, expanded state, and child metadata from the tree config.
Pro column and plugins
Mark one hierarchy column with tree: true, register TreeDataPlugin, and pass the tree config:
import {
TreeDataPlugin,
RowOrderPlugin,
RowSelectPlugin,
StickyCellsPlugin,
TREE_EXPAND_ALL_EVENT,
TREE_COLLAPSE_ALL_EVENT,
} from '@revolist/revogrid-pro';
const columns = [
{
prop: 'name',
name: 'Team',
size: 300,
tree: true,
sortable: true,
rowSelect: true,
rowDrag: true,
},
{ prop: 'role', name: 'Role', size: 160 },
{ prop: 'budget', name: 'Budget', size: 140 },
];
const plugins = [
TreeDataPlugin,
RowOrderPlugin,
RowSelectPlugin,
StickyCellsPlugin,
];
const tree = {
idField: 'id',
parentIdField: 'parentId',
rootParentId: null,
expandedRowIds: new Set(['eng']),
stickyParents: true,
};Framework wrappers pass these values as props or bindings. Plain TypeScript assigns them to the revo-grid element.
Expand and collapse controls
The Pro plugin exposes events for toolbar buttons and external controls:
grid.dispatchEvent(new CustomEvent(TREE_EXPAND_ALL_EVENT));
grid.dispatchEvent(new CustomEvent(TREE_COLLAPSE_ALL_EVENT));Use expandAll: true when every branch should open on first render. Use expandedRowIds when you want controlled initial state or persisted expansion.
Production guidance
- Keep IDs stable across refreshes so expanded state survives data updates.
- Use
parentId: nullor another explicitrootParentIdconsistently. - Keep only one tree column active; use normal columns for the rest of the row data.
- Combine tree data with virtualization for large hierarchies instead of rendering nested DOM lists.
- Add
StickyCellsPluginonly when sticky parent rows are useful for long branches. - Reconcile drag-and-drop changes in your application store before saving to the backend.
- Prefer flat server payloads for large trees; they are simpler to page, diff, validate, and persist.