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.
This commit is contained in:
RedwindA
2026-06-15 20:52:08 +08:00
parent 9bc1a53dea
commit 8477f6288b
@@ -29,15 +29,20 @@ type DataTableRowProps<TData> = {
getColumnClassName?: DataTableColumnClassName
} & Omit<React.ComponentProps<typeof TableRow>, 'children'>
type DataTableRowInnerProps<TData> = DataTableRowProps<TData> & {
isSelected: boolean
}
function DataTableRowInner<TData>({
row,
isSelected,
className,
getColumnClassName,
...rowProps
}: DataTableRowProps<TData>) {
}: DataTableRowInnerProps<TData>) {
return (
<TableRow
data-state={row.getIsSelected() ? 'selected' : undefined}
data-state={isSelected ? 'selected' : undefined}
className={className}
{...rowProps}
>
@@ -56,17 +61,25 @@ function DataTableRowInner<TData>({
)
}
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<TData>(props: DataTableRowProps<TData>) {
return (
<MemoizedDataTableRow {...props} isSelected={props.row.getIsSelected()} />
)
}
function renderCellContent<TData>(cell: Cell<TData, unknown>) {
const content = flexRender(cell.column.columnDef.cell, cell.getContext())
const textContent = getPrimitiveTextContent(content)