Skip to content

Hierarchical grids

View as Markdown

Hierarchical grids use multiple levels. The root level renders first. Child levels render under expanded parent rows.

const schema: GridSchema = {
rootLevel: "projects",
levels: {
projects: {
name: "projects",
rowHeaderColumn: "none",
childLevels: ["tasks"],
columns: [text({ id: "name", name: "Project", edit: "default" })],
options: {},
},
tasks: {
name: "tasks",
rowHeaderColumn: "none",
childLevels: [],
columns: [text({ id: "title", name: "Task", edit: "default" })],
options: {},
},
},
};

In memory, nested rows live under children:

const tree: TreeNode[] = [
{
rowKey: "p1",
levelName: "projects",
columns: { name: "Launch" },
children: {
tasks: [
{
rowKey: "t1",
levelName: "tasks",
columns: { title: "Draft checklist" },
},
],
},
},
];

For remote data, a child request should include the parent path and row key. The server can then load only the children for that parent.

Use hierarchy when the user needs to work with related rows in place. If the screen is only showing totals, subtotals, or drill links, a readonly result surface may be a better fit than an editable grid.

Use inMemoryGridDataSource() when the full tree is already available in browser state:

import {
GridLevel,
GridRuntimeProvider,
createGridRuntime,
inMemoryGridDataSource,
useGridRuntimeEffect,
type GridSchema,
type TreeNode,
} from "@sapporta/grid";
import { text } from "@sapporta/grid/column-preset";
const schema: GridSchema = {
rootLevel: "projects",
levels: {
projects: {
name: "projects",
rowHeaderColumn: "none",
columns: [text({ id: "name", name: "Project", edit: "default" })],
childLevels: ["tasks"],
options: {},
},
tasks: {
name: "tasks",
rowHeaderColumn: "none",
columns: [text({ id: "title", name: "Task", edit: "default" })],
childLevels: [],
options: {},
},
},
};
const tree: TreeNode[] = [
{
rowKey: "project-1",
levelName: "projects",
columns: { name: "Launch" },
children: {
tasks: [
{
rowKey: "task-1",
levelName: "tasks",
columns: { title: "Review plan" },
},
],
},
},
];
export function ProjectGrid() {
const runtime = useGridRuntimeEffect(
() =>
createGridRuntime({
schema,
dataSource: inMemoryGridDataSource({
schema,
tree,
levels: {
projects: {
sortMode: "client",
filterMode: "none",
paginationMode: "none",
},
tasks: {
sortMode: "client",
filterMode: "none",
paginationMode: "none",
},
},
}),
}),
[],
);
if (!runtime) return null;
return (
<GridRuntimeProvider runtime={runtime}>
<GridLevel path={runtime.root.path} />
</GridRuntimeProvider>
);
}

Path-like level ids such as projects.tasks can make a larger hierarchy easier to read, but they are still grid level ids. They do not have to match route paths, database names, or backend resource names.

Typecheck the example and exercise its visible loading, ready, interaction, and failure states. Use only public @sapporta/grid export paths. Continue with Grid Reference.