Skip to content

finding(plugin-dashboard): PivotTable's own data fallback re-runs the cross-tabulation memo on every render — the third site of #4629's class, outside that card's file surface #5562

Description

@os-sales

Measured while doing #4629's package sweep. Filed unassigned; out of that card's scope — #4629's declared file surface is ObjectDataTable.tsx and ObjectPivotTable.tsx only, and PivotTable.tsx is a third component with its own entry path. Duplicate-searched (keyword + PivotTable / per-render-literal / exhaustive-deps): nothing open. The relatives are #4618 (closed, PR #4623) and #4629 itself.

What

packages/plugin-dashboard/src/PivotTable.tsx carries two per-render array literals for the same value, both feeding the same memo:

// ~:146, the destructuring default
const {, data: rawData = [],} = schema;
// :176
const data = Array.isArray(rawData) ? rawData : [];
// :241 — the memo's dependency list
}, [data, rowField, columnField, valueField, aggregation]);

ESLint already reports it, and did so on origin/main in this repo's own pnpm lint run:

packages/plugin-dashboard/src/PivotTable.tsx
  176:9  warning  The 'data' conditional could make the dependencies of useMemo Hook (at line 241)
                  change on every render. To fix this, wrap the initialization of 'data' in its own
                  useMemo() Hook          react-hooks/exhaustive-deps

The memo is not a trivial one: it builds two ordered key sets, a bucket[row][col] = number[] map, the aggregated matrix, and the row/column/grand totals. All of it is rebuilt over nothing whenever schema.data is absent or a non-array.

What #4629 already closed, and what it did not

#4629's fix to ObjectPivotTable.tsx:154 does close the object-bound path: ObjectPivotTable hands finalSchema.data = finalData, and with finalData now a module-scope frozen empty the Array.isArray arm at :176 passes it through unchanged, so the memo holds. Pinned by ObjectPivotTable.stableEmptyRows.test.tsx.

What is left is the direct-use path — PivotTable rendered from a schema that declares no data key at all (the destructuring default at ~:146), or one whose data is a provider-config object. DashboardRenderer / DashboardGridLayout both construct pivot schemas, so this is reachable without ObjectPivotTable in the chain.

Fix shape

The same one #4618/PR #4623 established and #4629 applied twice: one module-scope Object.freeze([]) used for both the destructuring default and the Array.isArray fallback, so "no rows" is one stable value. data-table.tsx needs exactly this pair (EMPTY_COLUMNS / EMPTY_ROWS) for the same reason and is the reference.

Note for whoever takes it: assert on the memo's recompute count or the identity across renders, not on "the pivot renders correctly" — nothing renders wrong today, so a rendering assertion is green against the broken code. ObjectPivotTable.stableEmptyRows.test.tsx is a worked example of the identity form.

Severity

Observation-class, same as #4629: wasted work only. Unlike #4618 this cannot sustain a render loop — the churn feeds a memo, not a setState, and PivotTable holds no prop→state sync.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions