Part of the Table feature suite tracker #3639.
Feature area: Row grouping & hierarchy
Problem
useTableRowExpansionState walks the tree in three places — depthMap, the flattened data, and allExpandableKeys — and none of the walks guard against cycles. If getChildren ever returns an ancestor (cyclic data, or a self-referential row), the recursion never terminates → stack overflow / hung render.
// current: no ancestor tracking
function walk(items: T[], depth: number) {
for (const item of items) {
map.set(getRowKey(item), depth);
if (expandedKeys.has(getRowKey(item))) {
walk(getChildren(item), depth + 1); // recurses forever on a cycle
}
}
}
Fix
Adopt the ancestor-chain Set guard used in #3789's tree walks: track the ids on the current path and continue when an edge points back at an ancestor.
const path = new Set<string>();
function walk(items: T[], depth: number) {
for (const item of items) {
const key = getRowKey(item);
if (path.has(key)) continue; // cyclic edge — skip
map.set(key, depth);
if (expandedKeys.has(key)) {
path.add(key);
walk(getChildren(item), depth + 1);
path.delete(key);
}
}
}
Apply to all three walks. Add a cyclic-data unit test (a row whose children include itself / an ancestor) that asserts the walk terminates.
Surfaced during review of #3789.
Part of the Table feature suite tracker #3639.
Feature area: Row grouping & hierarchy
Problem
useTableRowExpansionStatewalks the tree in three places —depthMap, the flatteneddata, andallExpandableKeys— and none of the walks guard against cycles. IfgetChildrenever returns an ancestor (cyclic data, or a self-referential row), the recursion never terminates → stack overflow / hung render.Fix
Adopt the ancestor-chain
Setguard used in #3789's tree walks: track the ids on the current path andcontinuewhen an edge points back at an ancestor.Apply to all three walks. Add a cyclic-data unit test (a row whose children include itself / an ancestor) that asserts the walk terminates.
Surfaced during review of #3789.