fix(tablev2): keep RenderRow stable #1120
Conversation
…nder RenderRow was redefined inline on every render, so react-window saw a new component type and remounted every row (and cell) whenever the table re-rendered, making async cell content reload and flash. Read the row from react-window's itemData and volatile callbacks from refs so RenderRow keeps a stable identity. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hello jeanmarcmilletscality,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
Waiting for approvalThe following approvals are needed before I can proceed with the merge:
Peer approvals must include at least 1 approval from the following list: |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| const rows = data; | ||
| const row = data[index]; | ||
| prepareRowRef.current(row); | ||
| const onSingleRowSelected = onSingleRowSelectedRef.current; |
There was a problem hiding this comment.
onSingleRowSelected is captured from the ref at render time into a local variable, then used in the click handler ternary on line 169. The other refs (handleMultipleSelectedRowsRef, toggleAllRowsSelectedRef, selectedRowIdsRef) are correctly read from .current at click time — but this one isn't, so if the prop changes after the row renders, the click handler uses a stale reference and may pick the wrong branch (single-select vs multi-select).
Read the ref inside the click handler instead:
| const onSingleRowSelected = onSingleRowSelectedRef.current; | |
| const rowProps = { | |
| ...row.getRowProps({ | |
| /** | |
| * Note:We need to pass the style property to the row component. | |
| * Otherwise when we scroll down, the next rows are flashing | |
| * because they are re-rendered in loop. | |
| */ | |
| style: { ...style }, | |
| }), | |
| onClick: () => { | |
| const onSingleRowSelected = onSingleRowSelectedRef.current; | |
| if (onSingleRowSelected) { | |
| onSingleRowSelected(row); | |
| toggleAllRowsSelectedRef.current(false); | |
| setActiveRowId(row.id); | |
| } else { | |
| handleMultipleSelectedRowsRef.current( | |
| selectedRowIdsRef.current, | |
| rows, | |
| row, | |
| index, | |
| ); | |
| } | |
| }, |
The same change applies to the checkbox onClick on the selection column (line 207) — read onSingleRowSelectedRef.current there too.
| memo(({ index, style, data }: ListChildComponentProps<Row<DATA_ROW>[]>) => { | ||
| const row = data[index]; | ||
| prepareRowRef.current(row); | ||
| const onRowSelected = onRowSelectedRef.current; |
There was a problem hiding this comment.
Same issue as in MultiSelectableContent — onRowSelected is captured from the ref at render time and used in the click/keyDown handlers. Read it from the ref at call time instead:
| const onRowSelected = onRowSelectedRef.current; | |
| const rowProps = { | |
| ...rowProps, | |
| ...{ | |
| onClick: () => { | |
| const onRowSelected = onRowSelectedRef.current; | |
| if (onRowSelected) return onRowSelected(row); | |
| }, | |
| tabIndex: onRowSelectedRef.current ? 0 : undefined, | |
| onKeyDown: (event) => { | |
| const onRowSelected = onRowSelectedRef.current; |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| </TableRowMultiSelectable> | ||
| ); | ||
| }, areEqual), | ||
| [activeRowId, separationLineVariant], |
There was a problem hiding this comment.
activeRowId in the useMemo deps means every row click (via onSingleRowSelected) creates a new component type, causing react-window to unmount and remount all visible rows — the same flashing problem this PR fixes for other re-render triggers. The same applies to selectedId in SingleSelectableContent.
To keep the component stable across selection changes, move activeRowId into a ref (like the other volatile values) and pass it through itemData so areEqual can trigger re-renders without changing the component identity:
const activeRowIdRef = useRef<string | null>(null);
activeRowIdRef.current = activeRowId;
// in useMemo deps: remove activeRowId, keep only [separationLineVariant]
// in the row component: read activeRowIdRef.current instead of activeRowId
// pass activeRowId through itemData so areEqual detects the change:
// itemData={useMemo(() => ({ rows, activeRowId }), [rows, activeRowId])}This changes the itemData shape from Row[] to { rows, activeRowId }, which touches VirtualizedRows and TableRows types — a bigger refactor, so it may belong in a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| svg { | ||
| width: 1em; | ||
| height: 1em; | ||
| fill: #a14fbf; |
There was a problem hiding this comment.
Hardcoded hex color bypasses the theme system. This won't adapt to dark/light mode or custom themes. Use a theme token instead:
| fill: #a14fbf; | |
| fill: ${({ theme }) => theme.infoPrimary}; |
No description provided.