demo D76
Orphans and cycles in React
Data that is not quite a tree. Nothing is dropped
orphans: 'Unassigned'
This grid handles hierarchy data that is not quite a tree: parent references that point nowhere, and chains that loop back on themselves. It keeps every row visible and accounted for, gathering the strays under a named group rather than silently dropping them.
This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">
<div id="grid"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { createGrid } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
// Rows naming a parent that is not in the extract go to the named bucket, and
// a cycle is cut and its rows shown at the root, with one console warning
// rather than one per row. No row is lost either way.
const tree = { parentKey: 'parentId', orphans: 'Unassigned', label: 'name', title: 'Circuit' };
const columns = [
{ field: 'source', title: 'Source', filter: { type: 'set' }, layout: { width: 160 } },
{ field: 'parentId', title: 'Names as parent', layout: { width: 220 } },
{ field: 'charge', title: 'Monthly charge', type: 'number', format: { style: 'currency', currency: 'GBP', decimals: 0 }, total: 'sum', layout: { width: 180 } },
];
const rows = [/* a parent reference with orphans and a cycle */];
function App() {
return (
<LatticeGrid
rowKey="id"
selection="multiple"
grandTotalRow="bottom"
tree={tree}
toolPanel={{ side: 'left', panels: ['columns', 'filters', 'views', 'quick'],
actions: ['undo', 'redo', 'export', 'restore', 'maximise'], exportName: 'lattice-demo' }}
columns={columns}
rows={rows}
style={{ height: '540px' }}
/>
);
}
createRoot(document.getElementById('grid')).render(<App />);
</script>
Handling broken hierarchies: missing parents and cyclic references
Real hierarchy data is rarely a clean tree. A parent ID can point at a row that was filtered out upstream, deleted from the source system, or never existed in the first place, and occasionally a chain of parent references loops back on itself. A developer reaches for this when building tree data from a flat table sourced from a database join or a CSV export, where referential integrity was never guaranteed. Lattice Grid handles both cases without dropping rows: set orphans: 'Unassigned' and any row whose declared parent cannot be found is regrouped under a synthetic bucket with that label, rather than being silently excluded from the grid or, worse, from a total. Cycles are detected during tree construction, so a row that would otherwise reference its own descendant as a parent is broken out into the same fallback grouping instead of causing infinite recursion. Because this is a JavaScript data grid built for incremental updates, the orphan bucket recalculates only for the rows affected by a change, not the whole tree. This matters for accessibility as well as correctness: a screen reader announcing group counts should never encounter a total that silently excludes rows the user cannot otherwise account for.
What happens to rows with a missing or invalid parent ID in a tree grid?
Lattice Grid groups them under a fallback node rather than discarding them. Setting orphans: 'Unassigned' on the tree configuration names that node, so every row whose parent reference cannot be resolved, including rows caught in a circular reference, still appears in the grid, still counts toward totals, and remains visible to filtering and search.