Skip to content

Fix crash when row arrays contain undefined entries (TenantId TypeError)#250

Open
andrescodas wants to merge 1 commit intomicrosoft:mainfrom
andrescodas:andrescodas/tenant-id-crash
Open

Fix crash when row arrays contain undefined entries (TenantId TypeError)#250
andrescodas wants to merge 1 commit intomicrosoft:mainfrom
andrescodas:andrescodas/tenant-id-crash

Conversation

@andrescodas
Copy link

Problem

The application crashes with TypeError: Cannot read properties of undefined (reading 'TenantId') when rendering tables whose row arrays contain undefined or null entries. TenantId is a Kusto data column name — the real issue is that .map() iterates over row objects and one or more elements in the array are undefined, causing any property access to throw.

This affects users loading data from external sources (e.g., Kusto/database connectors) where the server response may include sparse or null row entries.

Fix

Added .filter(Boolean) guards to filter out undefined/null row entries before they are iterated or indexed. Applied defensively at both data ingestion points (API responses, reducers) and rendering paths:

Data ingestion / state management:

  • ComponentType.tsx — createDictTable(): filter rows before deriving column names and type inference
  • dfSlice.tsx — updateTableRows, updateMultipleTableRows, addDerivedField, removeDerivedField reducers
  • tableThunks.ts — loadExternalSource API response handling

Rendering:

  • SelectableDataGrid.tsx — row initialization, prop sync, SAMPLE_TABLE API response, and itemContent null guard
  • DataView.tsx — both virtual and non-virtual row paths, sampled rows for column width calculation
  • ReactTable.tsx — table body row rendering
  • VisualizationView.tsx — renderTableChart, createVisTableRowsLocal, checkChartAvailability, checkChartAvailabilityOnPreparedData, and display row fetch handler
  • EncodingBox.tsx — domain item extraction
  • DBTableManager.tsx — both sample_rows usage sites
  • RefreshDataDialog.tsx — column validation using .find(Boolean) instead of [0]

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens table ingestion and rendering paths against sparse row arrays by filtering out undefined/null entries before property access, preventing crashes like TypeError: Cannot read properties of undefined.

Changes:

  • Add defensive row sanitization ((rows || []).filter(Boolean) / .find(Boolean)) across reducers, thunks, and multiple table/visualization renderers.
  • Replace direct first-row indexing (rows[0]) with safe first-row selection to avoid empty/sparse-array failures.
  • Add null-guard in virtualized grid row rendering (itemContent) to prevent rendering exceptions.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/views/VisualizationView.tsx Filters sparse rows for table charts and availability checks; filters sampled rows returned from server.
src/views/SelectableDataGrid.tsx Ensures displayed rows are sanitized and adds a null-guard in the virtualized row renderer.
src/views/RefreshDataDialog.tsx Validates columns using the first non-null row instead of newRows[0].
src/views/ReactTable.tsx Filters out nullish rows before rendering paged table body rows.
src/views/EncodingBox.tsx Filters nullish rows before extracting domain values for encodings.
src/views/DataView.tsx Filters nullish rows before rendering and before sampling for column width calculation.
src/views/DBTableManager.tsx Filters sample_rows from DB metadata before building preview/load tables.
src/components/ComponentType.tsx Sanitizes rows in createDictTable() and uses sanitized rows for type inference.
src/app/tableThunks.ts Filters nullish rows from external-source fetch responses before creating tables.
src/app/dfSlice.tsx Adds safeNewRows for inference/hash computation when updating table rows.
Comments suppressed due to low confidence (1)

src/views/VisualizationView.tsx:675

  • setVisTableRows((data.rows || []).filter(Boolean)) filters the server response, but the cache stores the unfiltered data.rows. When the cache is hit later, setVisTableRows(cached.rows) will reintroduce undefined/null rows and can bring back the same rendering crash. Cache the filtered rows (and/or filter again when reading from displayRowsCache).
                    if (data.status == "success") {
                        setVisTableRows((data.rows || []).filter(Boolean));
                        setVisTableTotalRowCount(data.total_row_count);
                        setDataVersion(versionId);
                        // Cache for instant reuse on chart revisit
                        displayRowsCache.set(versionId, { rows: data.rows, totalCount: data.total_row_count });
                    } else {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +658 to 673
const safeNewRows = newRows.filter(Boolean);
for (let name of t.names) {
if (newRows.length > 0 && name in newRows[0]) {
if (safeNewRows.length > 0 && name in safeNewRows[0]) {
newMetadata[name] = {
...newMetadata[name],
type: inferTypeFromValueArray(newRows.map(r => r[name])),
type: inferTypeFromValueArray(safeNewRows.map(r => r[name])),
};
}
}
// Update lastRefreshed timestamp if source exists
const updatedSource = t.source ? { ...t.source, lastRefreshed: Date.now() } : undefined;
// Use provided content hash (from backend for virtual/DB tables) or compute locally
// For virtual tables, backend hash reflects full table; for stream tables, compute from actual rows
const newContentHash = providedContentHash || computeContentHash(newRows, t.names);
const newContentHash = providedContentHash || computeContentHash(safeNewRows, t.names);
return { ...t, rows: newRows, metadata: newMetadata, source: updatedSource, contentHash: newContentHash };
}
const updatedSource = t.source ? { ...t.source, lastRefreshed: Date.now() } : undefined;
const newContentHash = providedContentHash || computeContentHash(newRows, t.names);
const newContentHash = providedContentHash || computeContentHash(safeNewRows, t.names);
return { ...t, rows: newRows, metadata: newMetadata, source: updatedSource, contentHash: newContentHash };
Comment on lines 88 to 92
{rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.filter(Boolean)
.map((row, i) => {
return (
<TableRow hover tabIndex={-1} key={i} sx={{ background: i % 2 == 0 ? '#F0F0F0' : "none" }}>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants