Skip to content

Commit 2812679

Browse files
committed
fix(scrollToIndex): dynamic height support
1 parent a417a88 commit 2812679

File tree

2 files changed

+29
-14
lines changed

2 files changed

+29
-14
lines changed

examples/dynamic/src/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,16 @@ function GridVirtualizerDynamic({ rows, columns }) {
156156

157157
const [show, setShow] = React.useState(true);
158158

159+
const halfWay = Math.floor(rows.length / 2);
160+
159161
return (
160162
<>
161163
<button onClick={() => setShow(old => !old)}>Toggle</button>
162-
<button onClick={() => rowVirtualizer.scrollToIndex(5000)}>
163-
Scroll to 5000
164+
<button onClick={() => rowVirtualizer.scrollToIndex(halfWay)}>
165+
Scroll to index {halfWay}
166+
</button>
167+
<button onClick={() => rowVirtualizer.scrollToIndex(rows.length)}>
168+
Scroll to index {rows.length - 1}
164169
</button>
165170
{show ? (
166171
<div

src/index.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ export function useVirtual({
108108
}
109109

110110
const virtualItems = React.useMemo(() => {
111-
const { scrollOffset } = latestRef.current
112-
113111
const virtualItems = []
114112

115113
for (let i = startIndex; i <= endIndex; i++) {
@@ -118,13 +116,16 @@ export function useVirtual({
118116
const item = {
119117
...measurement,
120118
measureRef: el => {
119+
const { scrollOffset } = latestRef.current
120+
121121
if (el) {
122122
const { [sizeKey]: measuredSize } = el.getBoundingClientRect()
123123

124124
if (measuredSize !== item.size) {
125125
if (item.start < scrollOffset) {
126126
defaultScrollToFn(scrollOffset + (measuredSize - item.size))
127127
}
128+
128129
setMeasuredCache(old => ({
129130
...old,
130131
[i]: measuredSize,
@@ -155,11 +156,8 @@ export function useVirtual({
155156
outerSize,
156157
scrollOffset,
157158
scrollOffsetPlusOuterSize,
158-
totalSize,
159159
} = latestRef.current
160160

161-
offset = Math.max(0, Math.min(offset, totalSize - outerSize))
162-
163161
if (align === 'auto') {
164162
if (offset <= scrollOffset) {
165163
align = 'start'
@@ -181,19 +179,15 @@ export function useVirtual({
181179
[scrollToFn]
182180
)
183181

184-
const scrollToIndex = React.useCallback(
182+
const tryScrollToIndex = React.useCallback(
185183
(index, { align = 'auto', ...rest } = {}) => {
186184
const {
187185
measurements,
188186
scrollOffset,
189187
scrollOffsetPlusOuterSize,
190188
} = latestRef.current
191189

192-
const measurement = measurements[index]
193-
194-
if (!measurement) {
195-
return
196-
}
190+
const measurement = measurements[Math.max(0, Math.min(index, size - 1))]
197191

198192
if (align === 'auto') {
199193
if (measurement.end >= scrollOffsetPlusOuterSize) {
@@ -211,9 +205,25 @@ export function useVirtual({
211205
: align === 'end'
212206
? measurement.end
213207
: measurement.start
208+
214209
scrollToOffset(offset, { align, ...rest })
215210
},
216-
[scrollToOffset]
211+
[scrollToOffset, size]
212+
)
213+
214+
const scrollToIndex = React.useCallback(
215+
(...args) => {
216+
// We do a double request here because of
217+
// dynamic sizes which can cause offset shift
218+
// and end up in the wrong spot. Unfortunately,
219+
// we can't know about those dynamic sizes until
220+
// we try and render them. So double down!
221+
tryScrollToIndex(...args)
222+
requestAnimationFrame(() => {
223+
tryScrollToIndex(...args)
224+
})
225+
},
226+
[tryScrollToIndex]
217227
)
218228

219229
return {

0 commit comments

Comments
 (0)