Skip to content

useTableRowExpansion: add cycle guard to tree walks (stack overflow on cyclic data) #3971

Description

@humbertovirtudes

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.

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions