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
Measured while doing #4629's package sweep. Filed unassigned; out of that card's scope — #4629's declared file surface is
ObjectDataTable.tsxandObjectPivotTable.tsxonly, andPivotTable.tsxis 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.tsxcarries two per-render array literals for the same value, both feeding the same memo:ESLint already reports it, and did so on
origin/mainin this repo's ownpnpm lintrun: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 wheneverschema.datais absent or a non-array.What #4629 already closed, and what it did not
#4629's fix to
ObjectPivotTable.tsx:154does close the object-bound path:ObjectPivotTablehandsfinalSchema.data = finalData, and withfinalDatanow a module-scope frozen empty theArray.isArrayarm at:176passes it through unchanged, so the memo holds. Pinned byObjectPivotTable.stableEmptyRows.test.tsx.What is left is the direct-use path —
PivotTablerendered from a schema that declares nodatakey at all (the destructuring default at~:146), or one whosedatais a provider-config object.DashboardRenderer/DashboardGridLayoutboth construct pivot schemas, so this is reachable withoutObjectPivotTablein 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 theArray.isArrayfallback, so "no rows" is one stable value.data-table.tsxneeds 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.tsxis 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, andPivotTableholds no prop→state sync.Generated by Claude Code