Skip to content

Commit 0fa0b07

Browse files
authored
Avoid more unnecessary experiments table re-renders (#4503)
1 parent 984f7fe commit 0fa0b07

File tree

9 files changed

+103
-74
lines changed

9 files changed

+103
-74
lines changed

webview/src/experiments/components/table/Table.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import React, { useRef, useState, CSSProperties } from 'react'
1+
import React, { useRef, useState, CSSProperties, useEffect } from 'react'
22
import cx from 'classnames'
33
import { useDispatch } from 'react-redux'
44
import styles from './styles.module.scss'
55
import { TableHead } from './header/TableHead'
66
import { TableContent } from './body/TableContent'
77
import { InstanceProp } from '../../util/interfaces'
8-
import { clearSelectedRows } from '../../state/rowSelectionSlice'
8+
import {
9+
clearSelectedRows,
10+
updateRowOrder
11+
} from '../../state/rowSelectionSlice'
912

1013
export const Table: React.FC<InstanceProp> = ({ instance }) => {
1114
const dispatch = useDispatch()
@@ -15,6 +18,27 @@ export const Table: React.FC<InstanceProp> = ({ instance }) => {
1518

1619
const tableRef = useRef<HTMLTableElement>(null)
1720

21+
const { setColumnOrder, getHeaderGroups, getAllLeafColumns, getRowModel } =
22+
instance
23+
24+
const { rows, flatRows } = getRowModel()
25+
26+
useEffect(() => {
27+
dispatch(
28+
updateRowOrder(
29+
flatRows.map(
30+
({ depth, original: { branch, id, executorStatus, starred } }) => ({
31+
branch,
32+
depth,
33+
executorStatus,
34+
id,
35+
starred
36+
})
37+
)
38+
)
39+
)
40+
}, [dispatch, flatRows])
41+
1842
return (
1943
<div
2044
className={styles.tableContainer}
@@ -34,15 +58,17 @@ export const Table: React.FC<InstanceProp> = ({ instance }) => {
3458
}}
3559
>
3660
<TableHead
37-
instance={instance}
61+
columnOrder={getAllLeafColumns().map(({ id }) => id)}
62+
headerGroups={getHeaderGroups()}
63+
setColumnOrder={setColumnOrder}
3864
root={tableRef.current}
3965
setExpColumnNeedsShadow={setExpColumnNeedsShadow}
4066
setTableHeadHeight={setTableHeadHeight}
4167
/>
4268
<TableContent
43-
instance={instance}
44-
tableRef={tableRef}
69+
rows={rows}
4570
tableHeadHeight={tableHeadHeight}
71+
tableRef={tableRef}
4672
/>
4773
</table>
4874
</div>

webview/src/experiments/components/table/body/ExperimentGroup.tsx

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import React from 'react'
2-
import { RowContent } from './Row'
2+
import { TableRow } from './Row'
33
import styles from '../styles.module.scss'
44
import { RowProp } from '../../../util/interfaces'
55

6-
export const NestedRow: React.FC<RowProp> = ({ row }) => {
7-
return <RowContent row={row} className={styles.nestedRow} />
6+
export const NestedRow: React.FC<RowProp & { isExpanded: boolean }> = ({
7+
row,
8+
isExpanded
9+
}) => {
10+
return (
11+
<TableRow row={row} isExpanded={isExpanded} className={styles.nestedRow} />
12+
)
813
}

webview/src/experiments/components/table/body/Row.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import cx from 'classnames'
2-
import React, { useCallback, useMemo } from 'react'
2+
import React, { memo, useCallback, useMemo } from 'react'
33
import { useDispatch, useSelector } from 'react-redux'
44
import { EXPERIMENT_WORKSPACE_ID } from 'dvc/src/cli/dvc/contract'
55
import { StubCell, CellWrapper } from './Cell'
@@ -16,8 +16,9 @@ import {
1616
toggleRowSelected
1717
} from '../../../state/rowSelectionSlice'
1818

19-
export const RowContent: React.FC<RowProp & { className?: string }> = ({
19+
const Row: React.FC<RowProp & { className?: string; isExpanded: boolean }> = ({
2020
row,
21+
isExpanded,
2122
className
2223
}): JSX.Element => {
2324
const changes = useSelector(
@@ -27,10 +28,11 @@ export const RowContent: React.FC<RowProp & { className?: string }> = ({
2728
const { selectedRows } = useSelector(
2829
(state: ExperimentsState) => state.rowSelection
2930
)
30-
const { getVisibleCells, original, getIsExpanded, subRows } = row
31+
const { getVisibleCells, original, subRows } = row
3132
const { branch, displayColor, error, starred, id } = original
3233
const [stubCell, ...cells] = getVisibleCells()
3334
const isWorkspace = id === EXPERIMENT_WORKSPACE_ID
35+
3436
const changesIfWorkspace = isWorkspace ? changes : undefined
3537

3638
const isRowSelected = !!selectedRows[getCompositeId(id, branch)]
@@ -95,7 +97,7 @@ export const RowContent: React.FC<RowProp & { className?: string }> = ({
9597
plotColor={displayColor}
9698
starred={starred}
9799
isRowSelected={isRowSelected}
98-
showSubRowStates={!getIsExpanded() && !isWorkspace}
100+
showSubRowStates={!isExpanded && !isWorkspace}
99101
subRowStates={subRowStates}
100102
toggleExperiment={() => toggleExperiment(id)}
101103
toggleRowSelection={toggleRowSelection}
@@ -117,3 +119,5 @@ export const RowContent: React.FC<RowProp & { className?: string }> = ({
117119
</ContextMenu>
118120
)
119121
}
122+
123+
export const TableRow = memo(Row)

webview/src/experiments/components/table/body/TableBody.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,38 @@
11
import React from 'react'
22
import cx from 'classnames'
33
import { EXPERIMENT_WORKSPACE_ID } from 'dvc/src/cli/dvc/contract'
4-
import { ExperimentGroup } from './ExperimentGroup'
5-
import { RowContent } from './Row'
4+
import { NestedRow } from './NestedRow'
5+
import { TableRow } from './Row'
66
import { WorkspaceRowGroup } from './WorkspaceRowGroup'
77
import styles from '../styles.module.scss'
8-
import { InstanceProp, RowProp } from '../../../util/interfaces'
8+
import { RowProp } from '../../../util/interfaces'
99

10-
interface TableBodyProps extends RowProp, InstanceProp {
10+
interface TableBodyProps extends RowProp {
1111
root: HTMLElement | null
1212
tableHeaderHeight: number
1313
isLast?: boolean
1414
}
1515

1616
export const TableBody: React.FC<TableBodyProps> = ({
1717
row,
18-
instance,
1918
root,
2019
tableHeaderHeight,
2120
isLast
2221
}) => {
2322
const contentProps = {
23+
isExpanded: row.getIsExpanded(),
2424
key: row.id,
2525
row
2626
}
2727
const content =
2828
row.depth > 0 ? (
29-
<ExperimentGroup {...contentProps} />
29+
<NestedRow {...contentProps} />
3030
) : (
31-
<RowContent {...contentProps} />
31+
<TableRow {...contentProps} />
3232
)
3333

3434
return row.original.id === EXPERIMENT_WORKSPACE_ID ? (
35-
<WorkspaceRowGroup
36-
tableHeaderHeight={tableHeaderHeight}
37-
root={root}
38-
instance={instance}
39-
>
35+
<WorkspaceRowGroup tableHeaderHeight={tableHeaderHeight} root={root}>
4036
{content}
4137
</WorkspaceRowGroup>
4238
) : (

webview/src/experiments/components/table/body/TableContent.test.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const tableStateData = {
1616
}
1717

1818
jest.mock('../../../../shared/api')
19-
jest.mock('./ExperimentGroup')
19+
jest.mock('./NestedRow')
2020
jest.mock('./Row')
2121

2222
describe('TableContent', () => {
@@ -815,18 +815,36 @@ describe('TableContent', () => {
815815
} as unknown as Table<Experiment>
816816

817817
const renderTableContent = (rowsInstance = instance) => {
818+
const { rows, flatRows } = rowsInstance.getRowModel()
819+
818820
return render(
819821
<Provider
820822
store={configureStore({
821823
preloadedState: {
824+
rowSelection: {
825+
lastSelectedRowId: undefined,
826+
rowOrder: flatRows.map(
827+
({
828+
depth,
829+
original: { branch, id, executorStatus, starred }
830+
}) => ({
831+
branch,
832+
depth,
833+
executorStatus,
834+
id,
835+
starred
836+
})
837+
),
838+
selectedRows: {}
839+
},
822840
tableData: tableStateData
823841
},
824842
reducer: experimentsReducers
825843
})}
826844
>
827845
<table>
828846
<TableContent
829-
instance={rowsInstance}
847+
rows={rows}
830848
tableHeadHeight={50}
831849
tableRef={createRef()}
832850
/>

webview/src/experiments/components/table/body/TableContent.tsx

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,21 @@
1-
import React, { Fragment, RefObject, useEffect } from 'react'
2-
import { useDispatch } from 'react-redux'
1+
import React, { Fragment, RefObject } from 'react'
2+
import { Row } from '@tanstack/react-table'
3+
import { Experiment } from 'dvc/src/experiments/webview/contract'
34
import { TableBody } from './TableBody'
45
import { collectBranchWithRows } from './util'
56
import { BranchDivider } from './branchDivider/BranchDivider'
6-
import { InstanceProp } from '../../../util/interfaces'
7-
import { updateRowOrder } from '../../../state/rowSelectionSlice'
87

9-
interface TableContentProps extends InstanceProp {
8+
interface TableContentProps {
9+
rows: Row<Experiment>[]
1010
tableRef: RefObject<HTMLTableElement>
1111
tableHeadHeight: number
1212
}
1313

1414
export const TableContent: React.FC<TableContentProps> = ({
15-
instance,
16-
tableRef,
17-
tableHeadHeight
15+
rows,
16+
tableHeadHeight,
17+
tableRef
1818
}) => {
19-
const { rows, flatRows } = instance.getRowModel()
20-
const dispatch = useDispatch()
21-
22-
useEffect(() => {
23-
dispatch(
24-
updateRowOrder(
25-
flatRows.map(
26-
({ depth, original: { branch, id, executorStatus, starred } }) => ({
27-
branch,
28-
depth,
29-
executorStatus,
30-
id,
31-
starred
32-
})
33-
)
34-
)
35-
)
36-
}, [dispatch, flatRows])
37-
3819
return (
3920
<>
4021
{collectBranchWithRows(rows).map(([branch, branchRows]) => {
@@ -51,7 +32,6 @@ export const TableContent: React.FC<TableContentProps> = ({
5132
tableHeaderHeight={tableHeadHeight}
5233
root={tableRef.current}
5334
row={row}
54-
instance={instance}
5535
isLast={i === branchRows.length - 1}
5636
/>
5737
</Fragment>

webview/src/experiments/components/table/body/WorkspaceRowGroup.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import cx from 'classnames'
2-
import React, { PropsWithChildren } from 'react'
2+
import React, { PropsWithChildren, ReactNode } from 'react'
33
import { useInView } from 'react-intersection-observer'
4-
import { InstanceProp } from '../../../util/interfaces'
54
import styles from '../styles.module.scss'
65

7-
interface WorkspaceRowGroupProps extends InstanceProp {
6+
interface WorkspaceRowGroupProps {
87
root: HTMLElement | null
98
tableHeaderHeight: number
9+
children: ReactNode
1010
}
1111

1212
export const WorkspaceRowGroup: React.FC<

webview/src/experiments/components/table/header/TableHead.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Experiment } from 'dvc/src/experiments/webview/contract'
2-
import React, { DragEvent, useRef, useEffect } from 'react'
2+
import React, { DragEvent, useRef, useEffect, memo } from 'react'
33
import { useSelector, useDispatch } from 'react-redux'
4-
import { Table, Header } from '@tanstack/react-table'
4+
import {
5+
Header,
6+
HeaderGroup,
7+
Updater,
8+
ColumnOrderState
9+
} from '@tanstack/react-table'
510
import { MergedHeaderGroups } from './MergeHeaderGroups'
611
import { setDropTarget } from '../../../state/headerDropTargetSlice'
712
import { ExperimentsState } from '../../../store'
@@ -15,22 +20,22 @@ import styles from '../styles.module.scss'
1520
import { reorderColumns } from '../../../util/messages'
1621

1722
interface TableHeadProps {
18-
instance: Table<Experiment>
23+
headerGroups: HeaderGroup<Experiment>[]
24+
columnOrder: string[]
25+
setColumnOrder: (updater: Updater<ColumnOrderState>) => void
1926
root: HTMLElement | null
2027
setExpColumnNeedsShadow: (needsShadow: boolean) => void
2128
setTableHeadHeight: (height: number) => void
2229
}
2330

24-
export const TableHead = ({
25-
instance,
31+
const THead = ({
32+
columnOrder,
33+
headerGroups,
34+
setColumnOrder,
2635
root,
2736
setExpColumnNeedsShadow,
2837
setTableHeadHeight
2938
}: TableHeadProps) => {
30-
const { setColumnOrder, getHeaderGroups, getAllLeafColumns } = instance
31-
const headerGroups = getHeaderGroups()
32-
const allColumns = getAllLeafColumns()
33-
3439
const headerDropTargetId = useSelector(
3540
(state: ExperimentsState) => state.headerDropTarget
3641
)
@@ -58,7 +63,7 @@ export const TableHead = ({
5863
)
5964
if (displacerHeader) {
6065
draggingIds.current = leafColumnIds(displacerHeader)
61-
fullColumnOrder.current = allColumns.map(({ id }) => id)
66+
fullColumnOrder.current = columnOrder
6267
}
6368
}
6469

@@ -130,3 +135,5 @@ export const TableHead = ({
130135
</thead>
131136
)
132137
}
138+
139+
export const TableHead = memo(THead)

0 commit comments

Comments
 (0)