diff --git a/src/table/__tests__/columns-width.test.tsx b/src/table/__tests__/columns-width.test.tsx index 2b1b976697..7c3743321a 100644 --- a/src/table/__tests__/columns-width.test.tsx +++ b/src/table/__tests__/columns-width.test.tsx @@ -224,3 +224,28 @@ describe('with stickyHeader=true', () => { ]); }); }); + +test('does not measure column widths when re-rendering with unchanged columns', () => { + const getBoundingClientRectSpy = jest.spyOn(Element.prototype, 'getBoundingClientRect'); + const columns: TableProps.ColumnDefinition[] = [ + { id: 'id', header: 'id', cell: item => item.id, width: 150 }, + { id: 'text', header: 'text', cell: item => item.text, width: 200 }, + ]; + const { rerender } = renderTable( + + ); + + // Clear call count after initial render + getBoundingClientRectSpy.mockClear(); + + // Re-render with different items but same columns + const newItems = [ + { id: 1, text: 'updated' }, + { id: 2, text: 'new item' }, + ]; + rerender(
); + + expect(getBoundingClientRectSpy).not.toHaveBeenCalled(); + + getBoundingClientRectSpy.mockRestore(); +}); diff --git a/src/table/internal.tsx b/src/table/internal.tsx index 1abb05aff7..1b28bd0d84 100644 --- a/src/table/internal.tsx +++ b/src/table/internal.tsx @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { useCallback, useImperativeHandle, useRef } from 'react'; +import React, { useCallback, useImperativeHandle, useMemo, useRef } from 'react'; import clsx from 'clsx'; import { useContainerQuery } from '@cloudscape-design/component-toolkit'; @@ -294,11 +294,10 @@ const InternalTable = React.forwardRef( const { moveFocusDown, moveFocusUp, moveFocus } = useSelectionFocusMove(selectionType, allItems.length); const { onRowClickHandler, onRowContextMenuHandler } = useRowEvents({ onRowClick, onRowContextMenu }); - const visibleColumnDefinitions = getVisibleColumnDefinitions({ - columnDefinitions, - columnDisplay, - visibleColumns, - }); + const visibleColumnDefinitions = useMemo( + () => getVisibleColumnDefinitions({ columnDefinitions, columnDisplay, visibleColumns }), + [columnDefinitions, columnDisplay, visibleColumns] + ); const { isItemSelected, getSelectAllProps, getItemSelectionProps } = useSelection({ items: allItems, @@ -344,17 +343,20 @@ const InternalTable = React.forwardRef( headerIdRef.current = id; }, []); - const visibleColumnWidthsWithSelection: ColumnWidthDefinition[] = []; - const visibleColumnIdsWithSelection: PropertyKey[] = []; - if (hasSelection) { - visibleColumnWidthsWithSelection.push({ id: selectionColumnId, width: SELECTION_COLUMN_WIDTH }); - visibleColumnIdsWithSelection.push(selectionColumnId); - } - for (let columnIndex = 0; columnIndex < visibleColumnDefinitions.length; columnIndex++) { - const columnId = getColumnKey(visibleColumnDefinitions[columnIndex], columnIndex); - visibleColumnWidthsWithSelection.push({ ...visibleColumnDefinitions[columnIndex], id: columnId }); - visibleColumnIdsWithSelection.push(columnId); - } + const { visibleColumnWidthsWithSelection, visibleColumnIdsWithSelection } = useMemo(() => { + const widths: ColumnWidthDefinition[] = []; + const ids: PropertyKey[] = []; + if (hasSelection) { + widths.push({ id: selectionColumnId, width: SELECTION_COLUMN_WIDTH }); + ids.push(selectionColumnId); + } + for (let columnIndex = 0; columnIndex < visibleColumnDefinitions.length; columnIndex++) { + const columnId = getColumnKey(visibleColumnDefinitions[columnIndex], columnIndex); + widths.push({ ...visibleColumnDefinitions[columnIndex], id: columnId }); + ids.push(columnId); + } + return { visibleColumnWidthsWithSelection: widths, visibleColumnIdsWithSelection: ids }; + }, [hasSelection, visibleColumnDefinitions]); const stickyState = useStickyColumns({ visibleColumns: visibleColumnIdsWithSelection, diff --git a/src/table/use-column-widths.tsx b/src/table/use-column-widths.tsx index db41a79c8c..1bc1dab157 100644 --- a/src/table/use-column-widths.tsx +++ b/src/table/use-column-widths.tsx @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useResizeObserver, useStableCallback } from '@cloudscape-design/component-toolkit/internal'; import { getLogicalBoundingClientRect } from '@cloudscape-design/component-toolkit/internal'; @@ -35,18 +35,17 @@ function readWidths( } function updateWidths( - visibleColumns: readonly ColumnWidthDefinition[], + column: ColumnWidthDefinition | undefined, oldWidths: Map, newWidth: number, columnId: PropertyKey ) { - const column = visibleColumns.find(column => column.id === columnId); let minWidth = DEFAULT_COLUMN_WIDTH; if (typeof column?.width === 'number' && column.width < DEFAULT_COLUMN_WIDTH) { - minWidth = column?.width; + minWidth = column.width; } if (typeof column?.minWidth === 'number') { - minWidth = column?.minWidth; + minWidth = column.minWidth; } newWidth = Math.max(newWidth, minWidth); if (oldWidths.get(columnId) === newWidth) { @@ -83,6 +82,8 @@ export function ColumnWidthsProvider({ visibleColumns, resizableColumns, contain const containerWidthRef = useRef(0); const [columnWidths, setColumnWidths] = useState>(null); + const columnById = useMemo(() => new Map(visibleColumns.map(column => [column.id, column])), [visibleColumns]); + const cellsRef = useRef(new Map()); const stickyCellsRef = useRef(new Map()); const getCell = (columnId: PropertyKey): null | HTMLElement => cellsRef.current.get(columnId) ?? null; @@ -96,7 +97,7 @@ export function ColumnWidthsProvider({ visibleColumns, resizableColumns, contain }; const getColumnStyles = (sticky: boolean, columnId: PropertyKey): ColumnWidthStyle => { - const column = visibleColumns.find(column => column.id === columnId); + const column = columnById.get(columnId); if (!column) { return {}; } @@ -190,7 +191,8 @@ export function ColumnWidthsProvider({ visibleColumns, resizableColumns, contain }, []); function updateColumn(columnId: PropertyKey, newWidth: number) { - setColumnWidths(columnWidths => updateWidths(visibleColumns, columnWidths ?? new Map(), newWidth, columnId)); + const column = columnById.get(columnId); + setColumnWidths(columnWidths => updateWidths(column, columnWidths ?? new Map(), newWidth, columnId)); } return (