Description
Description
Currently, our grid component maps over rows and columns using array indexes (rowIndex
and columnIndex
) as React keys. When rows or columns are inserted, removed, or reordered, this causes React to remount entire <Row>
and <DroppableCell>
wrappers, even if the underlying <Tile>
components have stable tile.id
keys. This unnecessary remounting leads to lost component state, focus issues, and degraded performance.
Steps to Reproduce
- Render the grid with N rows and M columns.
- Each
<Row>
is keyed viakey={rowIndex}
. - Each
<DroppableCell>
inside the row is keyed viakey={columnIndex}
. - Change the data to insert or remove a row/column before existing ones.
- Observe that all affected rows/cells remount (losing state) instead of only the inserted/removed ones.
Expected Behavior
- Only the newly inserted or removed rows/cells should mount or unmount.
- Existing rows, cells, and
<Tile>
components should preserve their DOM, state, and focus when their position shifts.
Actual Behavior
- React treats all rows/cells after the insertion/removal point as new, tearing down and recreating them.
- Child
<Tile>
components unmount and remount, losing any local state or user focus.
Proposed Solution
-
Row keys: Use a stable identifier for each row (e.g.,
row.id
) instead of therowIndex
. -
Cell keys: Generate a unique key per cell that combines row and column context or uses
item.id
when present:const cellKey = item ? tile.id : `empty-${row.id}-${columnIndex}`; <DroppableCell key={cellKey} ...>
-
Retain
key={tile.id}
on the<Tile>
to preserve its identity across moves.
Implementing these changes will ensure React’s reconciliation only updates what truly changed, preserving component state and improving performance.