From 43d4bcb2c804cdb902daa73be1a7ce2272a93175 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 23 Jul 2026 10:39:18 -0400 Subject: [PATCH] fix(browse): guard Delete Row against not-yet-loaded record MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EditTableRowModal Delete Row button reads the primary-key value with `data[0]?.[primaryKey]`, but `data` is undefined while the record fetch is in flight — the parent passes `searchByIdData?.data` (see DatabaseTableView), and the footer (with Delete Row) renders regardless of whether `data` has loaded. The `?.` only guarded the `[primaryKey]` access, not `[0]`, so clicking Delete before the record resolved threw an unhandled TypeError: Cannot read properties of undefined (reading '0') on the table browse view (`/$organizationId/$clusterId/databases/$databaseName/$tableName/`), surfaced in Datadog RUM. Make the prop type honest (`data?: …[]`, matching the parent and the existing `data?.map` / `{data ? … : }` usages) and guard the read with `data?.[0]?.[primaryKey]`. Add a regression test that clicks Delete Row with `data={undefined}` and asserts it neither throws nor calls onDeleteRecord. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../databases/modals/EditTableRowModal.test.tsx | 16 +++++++++++++++- .../databases/modals/EditTableRowModal.tsx | 6 ++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/features/instance/databases/modals/EditTableRowModal.test.tsx b/src/features/instance/databases/modals/EditTableRowModal.test.tsx index b719036c1..3cb01c756 100644 --- a/src/features/instance/databases/modals/EditTableRowModal.test.tsx +++ b/src/features/instance/databases/modals/EditTableRowModal.test.tsx @@ -2,7 +2,7 @@ * @vitest-environment jsdom */ import { EditTableRowModal } from '@/features/instance/databases/modals/EditTableRowModal'; -import { cleanup, render, screen } from '@testing-library/react'; +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; // Monaco can't load in jsdom; stub the editor with a plain element that exposes the value it was @@ -86,6 +86,20 @@ describe('EditTableRowModal', () => { expect(screen.queryByRole('button', { name: /Delete Row/i })).toBeNull(); }); + it('does not throw when Delete Row is clicked before the record has loaded', () => { + // The parent passes `data={searchByIdData?.data}`, which is undefined while the + // record fetch is in flight — but the Delete Row button renders regardless. Clicking + // it then used to do an unguarded `data[0]` and throw an unhandled + // "Cannot read properties of undefined (reading '0')" (RUM, browse table view). + const onDeleteRecord = vi.fn(); + renderModal({ data: undefined, onDeleteRecord }); + + const deleteButton = screen.getByRole('button', { name: /Delete Row/i }); + expect(() => fireEvent.click(deleteButton)).not.toThrow(); + // Nothing to delete without a loaded record, so the delete is a no-op. + expect(onDeleteRecord).not.toHaveBeenCalled(); + }); + it('lets a normal row be edited and deleted', () => { renderModal(); diff --git a/src/features/instance/databases/modals/EditTableRowModal.tsx b/src/features/instance/databases/modals/EditTableRowModal.tsx index 8bf6198c4..766c4f0e0 100644 --- a/src/features/instance/databases/modals/EditTableRowModal.tsx +++ b/src/features/instance/databases/modals/EditTableRowModal.tsx @@ -37,7 +37,9 @@ export function EditTableRowModal({ /** Relationship/computed attribute names — read-only, so they are hidden from the editable JSON * (saving a record that assigns one fails, even with null). */ syntheticAttributes?: string[]; - data: { __createdtime__?: number; __updatedtime__?: number; [record: string]: unknown }[]; + /** Undefined while the record fetch is in flight (the parent passes `searchByIdData?.data`), so + * every read must tolerate it — the editor renders a loading state and the write actions guard it. */ + data?: { __createdtime__?: number; __updatedtime__?: number; [record: string]: unknown }[]; onSaveChanges: (data: Record[]) => void; onDeleteRecord: (data: unknown[]) => void; isUpdateTableRecordsPending: boolean; @@ -147,7 +149,7 @@ export function EditTableRowModal({ type="button" autoFocus={false} onClick={() => { - const primaryKeyValue = data[0]?.[primaryKey]; + const primaryKeyValue = data?.[0]?.[primaryKey]; if (primaryKeyValue != null) { onDeleteRecord([primaryKeyValue]); }