From 58c1d1a1f2994d49bdc758f484aa04082166a1e2 Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Tue, 24 Feb 2026 14:33:36 +0600 Subject: [PATCH 1/2] fix: row selection selects correct row when filtering is applied Use row.id instead of row.index for onRowSelect to fix incorrect row selection when data is filtered. Row selection state is keyed by row.id, so using the id directly ensures the correct row is selected regardless of filtering/sorting. Fixes #1107 Co-authored-by: Cursor --- .../components/data-grid-render-demo.tsx | 4 +- .../data-grid/data-grid-select-column.tsx | 4 +- src/hooks/test/use-data-grid.test.tsx | 53 ++++++++++++++++--- src/hooks/use-data-grid.ts | 49 ++++++++++------- src/types/data-grid.ts | 2 +- 5 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/app/data-grid-render/components/data-grid-render-demo.tsx b/src/app/data-grid-render/components/data-grid-render-demo.tsx index a7670df38..72a02e729 100644 --- a/src/app/data-grid-render/components/data-grid-render-demo.tsx +++ b/src/app/data-grid-render/components/data-grid-render-demo.tsx @@ -107,7 +107,7 @@ export function DataGridRenderDemo() { onCheckedChange={(value) => { const onRowSelect = table.options.meta?.onRowSelect; if (onRowSelect) { - onRowSelect(row.index, !!value, false); + onRowSelect(row.id, !!value, false); } else { row.toggleSelected(!!value); } @@ -117,7 +117,7 @@ export function DataGridRenderDemo() { event.preventDefault(); const onRowSelect = table.options.meta?.onRowSelect; if (onRowSelect) { - onRowSelect(row.index, !row.getIsSelected(), true); + onRowSelect(row.id, !row.getIsSelected(), true); } } }} diff --git a/src/components/data-grid/data-grid-select-column.tsx b/src/components/data-grid/data-grid-select-column.tsx index ab3ef8503..d019659c2 100644 --- a/src/components/data-grid/data-grid-select-column.tsx +++ b/src/components/data-grid/data-grid-select-column.tsx @@ -167,7 +167,7 @@ function DataGridSelectCell({ const onCheckedChange = React.useCallback( (value: boolean) => { if (meta?.onRowSelect) { - meta.onRowSelect(row.index, value, false); + meta.onRowSelect(row.id, value, false); } else { row.toggleSelected(value); } @@ -179,7 +179,7 @@ function DataGridSelectCell({ (event: React.MouseEvent) => { if (event.shiftKey) { event.preventDefault(); - meta?.onRowSelect?.(row.index, !row.getIsSelected(), true); + meta?.onRowSelect?.(row.id, !row.getIsSelected(), true); } }, [meta, row], diff --git a/src/hooks/test/use-data-grid.test.tsx b/src/hooks/test/use-data-grid.test.tsx index be5dd3511..4ef17ee51 100644 --- a/src/hooks/test/use-data-grid.test.tsx +++ b/src/hooks/test/use-data-grid.test.tsx @@ -1447,12 +1447,14 @@ describe("useDataGrid", () => { useDataGrid({ data: testData, columns: testColumns, + getRowId: (row) => row.id, }), { wrapper: createWrapper() }, ); + const firstRowId = result.current.table.getRowModel().rows[0]?.id; act(() => { - result.current.tableMeta.onRowSelect?.(0, true, false); + result.current.tableMeta.onRowSelect?.(firstRowId ?? "1", true, false); }); const rowSelection = result.current.table.getState().rowSelection; @@ -1465,18 +1467,23 @@ describe("useDataGrid", () => { useDataGrid({ data: testData, columns: testColumns, + getRowId: (row) => row.id, }), { wrapper: createWrapper() }, ); + const rows = result.current.table.getRowModel().rows; + const firstRowId = rows[0]?.id; + const thirdRowId = rows[2]?.id; + // Select first row act(() => { - result.current.tableMeta.onRowSelect?.(0, true, false); + result.current.tableMeta.onRowSelect?.(firstRowId ?? "1", true, false); }); // Select third row with shift act(() => { - result.current.tableMeta.onRowSelect?.(2, true, true); + result.current.tableMeta.onRowSelect?.(thirdRowId ?? "3", true, true); }); const rowSelection = result.current.table.getState().rowSelection; @@ -1489,23 +1496,55 @@ describe("useDataGrid", () => { useDataGrid({ data: testData, columns: testColumns, + getRowId: (row) => row.id, }), { wrapper: createWrapper() }, ); + const firstRowId = result.current.table.getRowModel().rows[0]?.id; + // Select row act(() => { - result.current.tableMeta.onRowSelect?.(0, true, false); + result.current.tableMeta.onRowSelect?.(firstRowId ?? "1", true, false); }); // Deselect row act(() => { - result.current.tableMeta.onRowSelect?.(0, false, false); + result.current.tableMeta.onRowSelect?.(firstRowId ?? "1", false, false); + }); + + const rowSelection = result.current.table.getState().rowSelection; + expect(rowSelection[firstRowId ?? "1"]).toBeFalsy(); + }); + + it("should select correct row when filtering is applied", () => { + const { result } = renderHook( + () => + useDataGrid({ + data: testData, + columns: testColumns, + getRowId: (row) => row.id, + initialState: { + columnFilters: [{ id: "name", value: "Tony" }], + }, + }), + { wrapper: createWrapper() }, + ); + + // With filter "name contains Tony", only Tony Hawk (id: "1") should be visible + const rows = result.current.table.getRowModel().rows; + expect(rows.length).toBe(1); + const visibleRowId = rows[0]?.id; + expect(visibleRowId).toBe("1"); + + // Select the visible (filtered) row + act(() => { + result.current.tableMeta.onRowSelect?.(visibleRowId ?? "1", true, false); }); const rowSelection = result.current.table.getState().rowSelection; - const row = result.current.table.getRowModel().rows[0]; - expect(rowSelection[row?.id ?? "0"]).toBeFalsy(); + expect(rowSelection["1"]).toBe(true); + expect(Object.keys(rowSelection).length).toBe(1); }); }); diff --git a/src/hooks/use-data-grid.ts b/src/hooks/use-data-grid.ts index 7ec11b045..22323e4ac 100644 --- a/src/hooks/use-data-grid.ts +++ b/src/hooks/use-data-grid.ts @@ -83,7 +83,7 @@ interface DataGridState { searchMatches: CellPosition[]; matchIndex: number; searchOpen: boolean; - lastClickedRowIndex: number | null; + lastClickedRowId: string | null; pasteDialog: PasteDialogState; } @@ -193,7 +193,7 @@ function useDataGrid({ searchMatches: [], matchIndex: -1, searchOpen: false, - lastClickedRowIndex: null, + lastClickedRowId: null, pasteDialog: { open: false, rowsNeeded: 0, @@ -1939,28 +1939,39 @@ function useDataGrid({ ); const onRowSelect = React.useCallback( - (rowIndex: number, selected: boolean, shiftKey: boolean) => { + (rowId: string, selected: boolean, shiftKey: boolean) => { const currentState = store.getState(); const rows = tableRef.current?.getRowModel().rows ?? []; - const currentRow = rows[rowIndex]; + const currentRowIndex = rows.findIndex((r) => r.id === rowId); + const currentRow = currentRowIndex >= 0 ? rows[currentRowIndex] : null; if (!currentRow) return; - if (shiftKey && currentState.lastClickedRowIndex !== null) { - const startIndex = Math.min(currentState.lastClickedRowIndex, rowIndex); - const endIndex = Math.max(currentState.lastClickedRowIndex, rowIndex); - - const newRowSelection: RowSelectionState = { - ...currentState.rowSelection, - }; - - for (let i = startIndex; i <= endIndex; i++) { - const row = rows[i]; - if (row) { - newRowSelection[row.id] = selected; + if (shiftKey && currentState.lastClickedRowId !== null) { + const lastClickedRowIndex = rows.findIndex( + (r) => r.id === currentState.lastClickedRowId, + ); + if (lastClickedRowIndex >= 0) { + const startIndex = Math.min(lastClickedRowIndex, currentRowIndex); + const endIndex = Math.max(lastClickedRowIndex, currentRowIndex); + + const newRowSelection: RowSelectionState = { + ...currentState.rowSelection, + }; + + for (let i = startIndex; i <= endIndex; i++) { + const row = rows[i]; + if (row) { + newRowSelection[row.id] = selected; + } } - } - onRowSelectionChange(newRowSelection); + onRowSelectionChange(newRowSelection); + } else { + onRowSelectionChange({ + ...currentState.rowSelection, + [currentRow.id]: selected, + }); + } } else { onRowSelectionChange({ ...currentState.rowSelection, @@ -1968,7 +1979,7 @@ function useDataGrid({ }); } - store.setState("lastClickedRowIndex", rowIndex); + store.setState("lastClickedRowId", rowId); }, [store, onRowSelectionChange], ); diff --git a/src/types/data-grid.ts b/src/types/data-grid.ts index 4a28b5116..167ab8061 100644 --- a/src/types/data-grid.ts +++ b/src/types/data-grid.ts @@ -77,7 +77,7 @@ declare module "@tanstack/react-table" { rowHeight?: RowHeightValue; onRowHeightChange?: (value: RowHeightValue) => void; onRowSelect?: ( - rowIndex: number, + rowId: string, checked: boolean, shiftKey: boolean, ) => void; From 61160b7bcfe68046240addce7dbb56668495fdcd Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Tue, 24 Feb 2026 14:38:55 +0600 Subject: [PATCH 2/2] chore: sync --- src/hooks/test/use-data-grid.test.tsx | 6 +++++- src/types/data-grid.ts | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hooks/test/use-data-grid.test.tsx b/src/hooks/test/use-data-grid.test.tsx index 4ef17ee51..6f4515d1e 100644 --- a/src/hooks/test/use-data-grid.test.tsx +++ b/src/hooks/test/use-data-grid.test.tsx @@ -1539,7 +1539,11 @@ describe("useDataGrid", () => { // Select the visible (filtered) row act(() => { - result.current.tableMeta.onRowSelect?.(visibleRowId ?? "1", true, false); + result.current.tableMeta.onRowSelect?.( + visibleRowId ?? "1", + true, + false, + ); }); const rowSelection = result.current.table.getState().rowSelection; diff --git a/src/types/data-grid.ts b/src/types/data-grid.ts index 167ab8061..772066a34 100644 --- a/src/types/data-grid.ts +++ b/src/types/data-grid.ts @@ -76,11 +76,7 @@ declare module "@tanstack/react-table" { getVisualRowIndex?: (rowId: string) => number | undefined; rowHeight?: RowHeightValue; onRowHeightChange?: (value: RowHeightValue) => void; - onRowSelect?: ( - rowId: string, - checked: boolean, - shiftKey: boolean, - ) => void; + onRowSelect?: (rowId: string, checked: boolean, shiftKey: boolean) => void; onDataUpdate?: (params: CellUpdate | Array) => void; onRowsDelete?: (rowIndices: number[]) => void | Promise; onColumnClick?: (columnId: string) => void;