Skip to content

Commit

Permalink
fix: Scrolling in virtualized tables is jumpy (#410)
Browse files Browse the repository at this point in the history
  • Loading branch information
dogmar authored Feb 15, 2023
1 parent 0d05611 commit 1ee8bf0
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 17 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@react-types/shared": "3.16.0",
"@tanstack/match-sorter-utils": "8.7.6",
"@tanstack/react-table": "8.7.9",
"@tanstack/react-virtual": "3.0.0-beta.48",
"@types/chroma-js": "2.1.4",
"chroma-js": "2.4.2",
"classnames": "2.3.2",
Expand Down
51 changes: 34 additions & 17 deletions src/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {
useReactTable,
} from '@tanstack/react-table'
import { rankItem } from '@tanstack/match-sorter-utils'
import type { VirtualItem } from 'react-virtual'
import { useVirtual } from 'react-virtual'
import type { VirtualItem } from '@tanstack/react-virtual'
import { useVirtualizer } from '@tanstack/react-virtual'
import styled from 'styled-components'

import Button from './Button'
Expand Down Expand Up @@ -57,7 +57,7 @@ export type TableProps =
scrollTopMargin?: number
virtualizeRows?: boolean
reactVirtualOptions?: Omit<
Parameters<typeof useVirtual>,
Parameters<typeof useVirtualizer>,
'parentRef' | 'size'
>
reactTableOptions?: Omit<
Expand Down Expand Up @@ -131,7 +131,7 @@ const Tbody = styled(TbodyUnstyled)(({ theme }) => ({
backgroundColor: theme.colors['fill-one'],
}))

const Tr = styled.tr<{clickable?: boolean, lighter?: boolean}>(({ theme, clickable = false, lighter = false }) => ({
const Tr = styled.tr<{ clickable?: boolean; lighter?: boolean }>(({ theme, clickable = false, lighter = false }) => ({
display: 'contents',
backgroundColor: lighter
? theme.colors['fill-one']
Expand Down Expand Up @@ -210,11 +210,7 @@ const Td = styled.td<{
stickyColumn: boolean
truncateColumn: boolean
}>(({
theme,
firstRow,
loose,
stickyColumn,
truncateColumn = false,
theme, firstRow, loose, stickyColumn, truncateColumn = false,
}) => ({
display: 'flex',
flexDirection: 'column',
Expand Down Expand Up @@ -388,7 +384,8 @@ function TableRef({
reactTableOptions,
onRowClick,
...props
}: TableProps, forwardRef: Ref<any>) {
}: TableProps,
forwardRef: Ref<any>) {
const tableContainerRef = useRef<HTMLDivElement>()
const [hover, setHover] = useState(false)
const [scrollTop, setScrollTop] = useState(0)
Expand Down Expand Up @@ -418,14 +415,29 @@ function TableRef({
})

const { rows: tableRows } = table.getRowModel()
const rowVirtualizer = useVirtual({
parentRef: tableContainerRef,
size: tableRows.length,
overscan: 40,
const rowVirtualizer = useVirtualizer({
count: tableRows.length,
overscan: 1,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 52,
measureElement: el => {
// Since <td>s are rendered with `display: contents`, we need to calculate
// row height from contents using Range
if (el?.getBoundingClientRect().height <= 0 && el?.hasChildNodes()) {
const range = document.createRange()

range.setStart(el, 0)
range.setEnd(el, el.childNodes.length)

return range.getBoundingClientRect().height
}

return el.getBoundingClientRect().height
},
...virtualizerOptions,
})
const { virtualItems: virtualRows, totalSize } = rowVirtualizer
const virtualRows = rowVirtualizer.getVirtualItems()
const virtualHeight = rowVirtualizer.getTotalSize()

const { paddingTop, paddingBottom } = useMemo(() => ({
paddingTop:
Expand All @@ -434,10 +446,10 @@ function TableRef({
: 0,
paddingBottom:
virtualizeRows && virtualRows.length > 0
? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0)
? virtualHeight - (virtualRows?.[virtualRows.length - 1]?.end || 0)
: 0,
}),
[totalSize, virtualRows, virtualizeRows])
[virtualHeight, virtualRows, virtualizeRows])

const headerGroups = useMemo(() => table.getHeaderGroups(), [table])

Expand Down Expand Up @@ -525,6 +537,11 @@ function TableRef({
onClick={e => onRowClick?.(e, row)}
lighter={i % 2 === 0}
clickable={!!onRowClick}
// data-index is required for virtual scrolling to work
data-index={row.index}
{...(virtualizeRows
? { ref: rowVirtualizer.measureElement }
: {})}
>
{row.getVisibleCells().map(cell => (
<Td
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2423,6 +2423,7 @@ __metadata:
"@storybook/testing-library": 0.0.14-next.0
"@tanstack/match-sorter-utils": 8.7.6
"@tanstack/react-table": 8.7.9
"@tanstack/react-virtual": 3.0.0-beta.48
"@types/chroma-js": 2.1.4
"@types/react-transition-group": 4.4.5
"@types/styled-components": 5.1.26
Expand Down Expand Up @@ -4881,13 +4882,31 @@ __metadata:
languageName: node
linkType: hard

"@tanstack/react-virtual@npm:3.0.0-beta.48":
version: 3.0.0-beta.48
resolution: "@tanstack/react-virtual@npm:3.0.0-beta.48"
dependencies:
"@tanstack/virtual-core": 3.0.0-beta.48
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: 3c0d75570b2b316edbf4f658f7fc398a63467d305cd3b69450febd9ab470b65f00948561c9e8f315a6756564c5164e51ff276f1d2e76c196c7a6110ef115afea
languageName: node
linkType: hard

"@tanstack/table-core@npm:8.7.9":
version: 8.7.9
resolution: "@tanstack/table-core@npm:8.7.9"
checksum: 78d2314928c29559088e4bada0248cc7f94e93756e1a2c1f37a651db30276e9ae960d647bd3a61b67b3f0f9f7e4dec5dd58eb49b8adb80ee5952ef417b6e581f
languageName: node
linkType: hard

"@tanstack/virtual-core@npm:3.0.0-beta.48":
version: 3.0.0-beta.48
resolution: "@tanstack/virtual-core@npm:3.0.0-beta.48"
checksum: 2bc41ad00aaeb7c07ec1ba5987e795aacef01e1bc32ca75c65f3357aa2b202bfd0aece73b4eb79241d0166ee19956e99e3789ee0edbb55cafb2714adde9b07c2
languageName: node
linkType: hard

"@testing-library/dom@npm:^8.3.0":
version: 8.19.0
resolution: "@testing-library/dom@npm:8.19.0"
Expand Down

0 comments on commit 1ee8bf0

Please sign in to comment.