Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,11 @@ const {
- Optional
- This function receives the index of each item and should return the item's unique ID.
- This function should be passed whenever dynamic measurement rendering is enabled and the size or order of items in the list changes.

- `rangeExtractor: Function(range: {start: number; end: number; overscan: number; size: number}) => number[]`
- Optional
- Defaults to `defaultRangeExtractor`, is exported
- **Must be memoized using `React.useCallback()`**
- This function receives visible range parameters and should return array of indexes to render
### Returns

- `virtualItems: Array<item>`
Expand Down
46 changes: 31 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ const defaultMeasureSize = (el, horizontal) => {
return el[key]
}

export const defaultRangeExtractor = range => {
const start = Math.max(range.start - range.overscan, 0)
const end = Math.min(range.end + range.overscan, range.size - 1)

const arr = []

for (let i = start; i <= end; i++) {
arr.push(i)
}

return arr
}

export function useVirtual({
size = 0,
estimateSize = defaultEstimateSize,
Expand All @@ -27,6 +40,7 @@ export function useVirtual({
scrollOffsetFn,
keyExtractor = defaultKeyExtractor,
measureSize = defaultMeasureSize,
rangeExtractor = defaultRangeExtractor,
}) {
const sizeKey = horizontal ? 'width' : 'height'
const scrollKey = horizontal ? 'scrollLeft' : 'scrollTop'
Expand Down Expand Up @@ -74,7 +88,6 @@ export function useVirtual({

const totalSize = (measurements[size - 1]?.end || 0) + paddingEnd

latestRef.current.overscan = overscan
latestRef.current.measurements = measurements
latestRef.current.outerSize = outerSize
latestRef.current.totalSize = totalSize
Expand Down Expand Up @@ -120,10 +133,17 @@ export function useVirtual({
measureSizeRef.current = measureSize

const virtualItems = React.useMemo(() => {
const indexes = rangeExtractor({
start: range.start,
end: range.end,
overscan,
size: measurements.length,
})

const virtualItems = []
const end = Math.min(range.end, measurements.length - 1)

for (let i = range.start; i <= end; i++) {
for (let k = 0, len = indexes.length; k < len; k++) {
const i = indexes[k]
const measurement = measurements[i]

const item = {
Expand Down Expand Up @@ -153,12 +173,14 @@ export function useVirtual({

return virtualItems
}, [
range.start,
range.end,
measurements,
horizontal,
defaultScrollToFn,
horizontal,
keyExtractor,
measurements,
overscan,
range.end,
range.start,
rangeExtractor,
])

const mountedRef = React.useRef()
Expand Down Expand Up @@ -272,10 +294,7 @@ const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
}
}

function calculateRange(
{ overscan, measurements, outerSize, scrollOffset },
prevRange
) {
function calculateRange({ measurements, outerSize, scrollOffset }, prevRange) {
const size = measurements.length - 1
const getOffset = index => measurements[index].start

Expand All @@ -286,10 +305,7 @@ function calculateRange(
end++
}

start = Math.max(start - overscan, 0)
end = Math.min(end + overscan, size)

if (!prevRange || prevRange.start !== start || prevRange.end !== end) {
if (prevRange.start !== start || prevRange.end !== end) {
return { start, end }
}

Expand Down
9 changes: 9 additions & 0 deletions src/tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ function List({
onRef,
parentRef,
useVirtual,
rangeExtractor,
}) {
const rowVirtualizer = useVirtual({
size,
parentRef,
overscan,
useObserver: React.useCallback(() => ({ height, width }), [height, width]),
rangeExtractor,
})

return (
Expand Down Expand Up @@ -123,4 +125,11 @@ describe('useVirtual list', () => {
expect(screen.queryByText('Row 15')).toBeInTheDocument()
expect(screen.queryByText('Row 16')).not.toBeInTheDocument()
})
it('should use rangeExtractor', () => {
render(<List {...props} rangeExtractor={() => [0, 1]} />)

expect(screen.queryByText('Row 0')).toBeInTheDocument()
expect(screen.queryByText('Row 1')).toBeInTheDocument()
expect(screen.queryByText('Row 2')).not.toBeInTheDocument()
})
})
12 changes: 11 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ export type VirtualItem = {
measureRef: (el: HTMLElement | null) => void
}

export interface Range {
start: number
end: number
overscan: number
size: number
}

declare function defaultRangeExtractor(range: Range): number[]

declare function useVirtual<T>(options: {
size: number
parentRef: React.RefObject<T>
Expand All @@ -37,6 +46,7 @@ declare function useVirtual<T>(options: {
keyExtractor?: (index: number) => number | string
onScrollElement?: React.RefObject<HTMLElement>
scrollOffsetFn?: (event?: Event) => number
rangeExtractor?: (range: Range) => number[]
}): {
virtualItems: VirtualItem[]
totalSize: number
Expand All @@ -45,4 +55,4 @@ declare function useVirtual<T>(options: {
measure: () => void
}

export { useVirtual }
export { defaultRangeExtractor, useVirtual }