From 8477f6288b7277388c1f46fd4788d8799c3094e9 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Mon, 15 Jun 2026 20:52:08 +0800 Subject: [PATCH 1/2] fix(data-table): capture row selection state in memo comparison TanStack row objects may keep a stable reference while their selection state changes, so reading row.getIsSelected() inside the React.memo comparator could miss updates. Capture isSelected as an explicit prop and compare it instead, ensuring rows re-render when selection toggles. --- .../data-table/core/data-table-row.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/web/default/src/components/data-table/core/data-table-row.tsx b/web/default/src/components/data-table/core/data-table-row.tsx index 9973e419..23e08052 100644 --- a/web/default/src/components/data-table/core/data-table-row.tsx +++ b/web/default/src/components/data-table/core/data-table-row.tsx @@ -29,15 +29,20 @@ type DataTableRowProps = { getColumnClassName?: DataTableColumnClassName } & Omit, 'children'> +type DataTableRowInnerProps = DataTableRowProps & { + isSelected: boolean +} + function DataTableRowInner({ row, + isSelected, className, getColumnClassName, ...rowProps -}: DataTableRowProps) { +}: DataTableRowInnerProps) { return ( @@ -56,17 +61,25 @@ function DataTableRowInner({ ) } -export const DataTableRow = React.memo(DataTableRowInner, (prev, next) => { +const MemoizedDataTableRow = React.memo(DataTableRowInner, (prev, next) => { // Skip re-render when only the getColumnClassName reference changed but the - // row identity and selection state are the same — callers rarely stabilize - // this callback, so excluding it from comparison avoids unnecessary renders. + // row identity and captured selection state are the same. Callers rarely + // stabilize this callback, so excluding it from comparison avoids unnecessary + // renders. Do not read row.getIsSelected() here: TanStack row objects may keep + // a stable reference while their selection state changes. return ( prev.row === next.row && prev.className === next.className && - prev.row.getIsSelected() === next.row.getIsSelected() + prev.isSelected === next.isSelected ) }) as typeof DataTableRowInner +export function DataTableRow(props: DataTableRowProps) { + return ( + + ) +} + function renderCellContent(cell: Cell) { const content = flexRender(cell.column.columnDef.cell, cell.getContext()) const textContent = getPrimitiveTextContent(content) From 179f69dfcaa232600157e8602412eb6a8a744732 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Mon, 15 Jun 2026 21:11:46 +0800 Subject: [PATCH 2/2] fix(data-table): compare column class callback --- .../src/components/data-table/core/data-table-row.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/web/default/src/components/data-table/core/data-table-row.tsx b/web/default/src/components/data-table/core/data-table-row.tsx index 23e08052..0730380e 100644 --- a/web/default/src/components/data-table/core/data-table-row.tsx +++ b/web/default/src/components/data-table/core/data-table-row.tsx @@ -62,14 +62,12 @@ function DataTableRowInner({ } const MemoizedDataTableRow = React.memo(DataTableRowInner, (prev, next) => { - // Skip re-render when only the getColumnClassName reference changed but the - // row identity and captured selection state are the same. Callers rarely - // stabilize this callback, so excluding it from comparison avoids unnecessary - // renders. Do not read row.getIsSelected() here: TanStack row objects may keep - // a stable reference while their selection state changes. + // Do not read row.getIsSelected() here: TanStack row objects may keep a stable + // reference while their selection state changes. return ( prev.row === next.row && prev.className === next.className && + prev.getColumnClassName === next.getColumnClassName && prev.isSelected === next.isSelected ) }) as typeof DataTableRowInner