Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

🐛 Bug Report: “Maximum update depth exceeded” when using onChange with Virtualizer

オープン
#1,076 コメント 2 件 リアクション 3 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
45/100
issue の種類
バグ
明瞭さ
説明が足りない
活発さ
静か
技術スタック
react, typescript

調査の方向性

報告されている useVirtualizer の設定と、Virtualizer.notify、resizeItem、_measureElement を経由するコールスタックから始め、外側のコンテナのサイズを変更して失敗を再現します。報告にはリポジトリのソースファイルやテストが記載されていないため、まず React の virtualizer 実装と既存のリサイズテストを特定します。リサイズによって maximum-update-depth ループが発生せず、通常の仮想化動作が維持されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

Describe the bug

🐛 Bug Report: “Maximum update depth exceeded” when inner using onChange with Virtualizer

Package: @tanstack/react-virtual
Version: (e.g. 3.x.x — please replace with your actual version)```js

🧩 Description

Note: I did not provide an onChange prop to the Virtualizer.
The infinite update loop is triggered when the outer container’s dimensions change (for example, when resizing the browser window or the parent element).

When using the onChange callback in useVirtualizer, I encountered a “Maximum update depth exceeded” error caused by an apparent infinite update loop.

It seems that when the Virtualizer calls onChange → my component updates React state → this triggers a re-measure or resize → Virtualizer calls onChange again, leading to an infinite render cycle.

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
at throwIfInfiniteUpdateLoopDetected (react-dom.development.js:26793:11)
at getRootForUpdatedFiber (react-dom.development.js:7627:3)
at enqueueConcurrentHookUpdate (react-dom.development.js:7518:10)
at dispatchReducerAction (react-dom.development.js:12998:16)
at Object.onChange (index.js:37:9)
at Virtualizer.notify (index.js:281:65)
at Virtualizer.resizeItem (index.js:563:14)
at Virtualizer._measureElement (index.js:541:14)
at Virtualizer.measureElement (index.js:576:12)
at commitAttachRef (react-dom.development.js:21677:37)
at safelyAttachRef (react-dom.development.js:20803:5)

使用的代码
```jsx
  const contentRef = useRef<HTMLDivElement>(null);
  const contentSize = useSize(contentRef);

  const virtualOptions = useMemo(() => ({
    count: list.length,
    getScrollElement: () => scrollbarRef.current?.getScrollElement() || null,
    estimateSize: () => 36, 
    overscan: 45,
    scrollMargin: contentSize?.height || 0,
    enabled: true,
  }), [
    contentSize?.height,
    list.length,
  ]);

  const rowVirtualizer = useVirtualizer(virtualOptions);
  const virtualItems = rowVirtualizer.getVirtualItems();

const getRect = (element) => {
    const {offsetWidth, offsetHeight} = element;
    return {
        width: offsetWidth,
        height: offsetHeight
    };
}
;
const defaultKeyExtractor = (index) => index;
const defaultRangeExtractor = (range) => {
    const start = Math.max(range.startIndex - range.overscan, 0);
    const end = Math.min(range.endIndex + range.overscan, range.count - 1);
    const arr = [];
    for (let i = start; i <= end; i++) {
        arr.push(i);
    }
    return arr;
}
;
const observeElementRect = (instance, cb) => {
    const element = instance.scrollElement;
    if (!element) {
        return;
    }
    const targetWindow = instance.targetWindow;
    if (!targetWindow) {
        return;
    }
    const handler = (rect) => {
        const {width, height} = rect;
        cb({
            width: Math.round(width),
            height: Math.round(height)
        });
    }
    ;
    handler(getRect(element));
    if (!targetWindow.ResizeObserver) {
        return () => {}
        ;
    }
    const observer = new targetWindow.ResizeObserver( (entries) => {
        const run = () => {
            const entry = entries[0];
            if (entry == null ? void 0 : entry.borderBoxSize) {
                const box = entry.borderBoxSize[0];
                if (box) {
                    handler({
                        width: box.inlineSize,
                        height: box.blockSize
                    });
                    return;
                }
            }
            handler(getRect(element));
        }
        ;
        instance.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
    }
    );
    observer.observe(element, {
        box: "border-box"
    });
    return () => {
        observer.unobserve(element);
    }
    ;
}
;
const addEventListenerOptions = {
    passive: true
};
const observeWindowRect = (instance, cb) => {
    const element = instance.scrollElement;
    if (!element) {
        return;
    }
    const handler = () => {
        cb({
            width: element.innerWidth,
            height: element.innerHeight
        });
    }
    ;
    handler();
    element.addEventListener("resize", handler, addEventListenerOptions);
    return () => {
        element.removeEventListener("resize", handler);
    }
    ;
}
;
const supportsScrollend = typeof window == "undefined" ? true : "onscrollend"in window;
const observeElementOffset = (instance, cb) => {
    const element = instance.scrollElement;
    if (!element) {
        return;
    }
    const targetWindow = instance.targetWindow;
    if (!targetWindow) {
        return;
    }
    let offset = 0;
    const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : (0,
    _utils_js__WEBPACK_IMPORTED_MODULE_0__.debounce)(targetWindow, () => {
        cb(offset, false);
    }
    , instance.options.isScrollingResetDelay);
    const createHandler = (isScrolling) => () => {
        const {horizontal, isRtl} = instance.options;
        offset = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"];
        fallback();
        cb(offset, isScrolling);
    }
    ;
    const handler = createHandler(true);
    const endHandler = createHandler(false);
    endHandler();
    element.addEventListener("scroll", handler, addEventListenerOptions);
    const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
    if (registerScrollendEvent) {
        element.addEventListener("scrollend", endHandler, addEventListenerOptions);
    }
    return () => {
        element.removeEventListener("scroll", handler);
        if (registerScrollendEvent) {
            element.removeEventListener("scrollend", endHandler);
        }
    }
    ;
}
;
const observeWindowOffset = (instance, cb) => {
    const element = instance.scrollElement;
    if (!element) {
        return;
    }
    const targetWindow = instance.targetWindow;
    if (!targetWindow) {
        return;
    }
    let offset = 0;
    const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : (0,
    _utils_js__WEBPACK_IMPORTED_MODULE_0__.debounce)(targetWindow, () => {
        cb(offset, false);
    }
    , instance.options.isScrollingResetDelay);
    const createHandler = (isScrolling) => () => {
        offset = element[instance.options.horizontal ? "scrollX" : "scrollY"];
        fallback();
        cb(offset, isScrolling);
    }
    ;
    const handler = createHandler(true);
    const endHandler = createHandler(false);
    endHandler();
    element.addEventListener("scroll", handler, addEventListenerOptions);
    const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
    if (registerScrollendEvent) {
        element.addEventListener("scrollend", endHandler, addEventListenerOptions);
    }
    return () => {
        element.removeEventListener("scroll", handler);
        if (registerScrollendEvent) {
            element.removeEventListener("scrollend", endHandler);
        }
    }
    ;
}
;
const measureElement = (element, entry, instance) => {
    if (entry == null ? void 0 : entry.borderBoxSize) {
        const box = entry.borderBoxSize[0];
        if (box) {
            const size = Math.round(box[instance.options.horizontal ? "inlineSize" : "blockSize"]);
            return size;
        }
    }
    return element[instance.options.horizontal ? "offsetWidth" : "offsetHeight"];
}
;
const windowScroll = (offset, {adjustments=0, behavior}, instance) => {
    var _a, _b;
    const toOffset = offset + adjustments;
    (_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
        [instance.options.horizontal ? "left" : "top"]: toOffset,
        behavior
    });
}
;
const elementScroll = (offset, {adjustments=0, behavior}, instance) => {
    var _a, _b;
    const toOffset = offset + adjustments;
    (_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
        [instance.options.horizontal ? "left" : "top"]: toOffset,
        behavior
    });
}
;
class Virtualizer {
    constructor(opts) {
        this.unsubs = [];
        this.scrollElement = null;
        this.targetWindow = null;
        this.isScrolling = false;
        this.measurementsCache = [];
        this.itemSizeCache = /* @__PURE__ */
        new Map();
        this.pendingMeasuredCacheIndexes = [];
        this.scrollRect = null;
        this.scrollOffset = null;
        this.scrollDirection = null;
        this.scrollAdjustments = 0;
        this.elementsCache = /* @__PURE__ */
        new Map();
        this.observer = /* @__PURE__ */
        ( () => {
            let _ro = null;
            const get = () => {
                if (_ro) {
                    return _ro;
                }
                if (!this.targetWindow || !this.targetWindow.ResizeObserver) {
                    return null;
                }
                return _ro = new this.targetWindow.ResizeObserver( (entries) => {
                    entries.forEach( (entry) => {
                        const run = () => {
                            this._measureElement(entry.target, entry);
                        }
                        ;
                        this.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
                    }
                    );
                }
                );
            }
            ;
            return {
                disconnect: () => {
                    var _a;
                    (_a = get()) == null ? void 0 : _a.disconnect();
                    _ro = null;
                }
                ,
                observe: (target) => {
                    var _a;
                    return (_a = get()) == null ? void 0 : _a.observe(target, {
                        box: "border-box"
                    });
                }
                ,
                unobserve: (target) => {
                    var _a;
                    return (_a = get()) == null ? void 0 : _a.unobserve(target);
                }
            };
        }
        )();
        this.range = null;
        this.setOptions = (opts2) => {
            Object.entries(opts2).forEach( ([key,value]) => {
                if (typeof value === "undefined")
                    delete opts2[key];
            }
            );
            this.options = {
                debug: false,
                initialOffset: 0,
                overscan: 1,
                paddingStart: 0,
                paddingEnd: 0,
                scrollPaddingStart: 0,
                scrollPaddingEnd: 0,
                horizontal: false,
                getItemKey: defaultKeyExtractor,
                rangeExtractor: defaultRangeExtractor,
                onChange: () => {}
                ,
                measureElement,
                initialRect: {
                    width: 0,
                    height: 0
                },
                scrollMargin: 0,
                gap: 0,
                indexAttribute: "data-index",
                initialMeasurementsCache: [],
                lanes: 1,
                isScrollingResetDelay: 150,
                enabled: true,
                isRtl: false,
                useScrollendEvent: false,
                useAnimationFrameWithResizeObserver: false,
                ...opts2
            };
        }
        ;
        this.notify = (sync) => {
            var _a, _b;
            (_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this, sync);
        }
        ;
        this.maybeNotify = (0,
        _utils_js__WEBPACK_IMPORTED_MODULE_0__.memo)( () => {
            this.calculateRange();
            return [this.isScrolling, this.range ? this.range.startIndex : null, this.range ? this.range.endIndex : null];
        }
        , (isScrolling) => {
            this.notify(isScrolling);
        }
        , {
            key: true && "maybeNotify",
            debug: () => this.options.debug,
            initialDeps: [this.isScrolling, this.range ? this.range.startIndex : null, this.range ? this.range.endIndex : null]
        });
        this.cleanup = () => {
            this.unsubs.filter(Boolean).forEach( (d) => d());
            this.unsubs = [];
            this.observer.disconnect();
            this.scrollElement = null;
            this.targetWindow = null;
        }
        ;
        this._didMount = () => {
            return () => {
                this.cleanup();
            }
            ;
        }
        ;
        this._willUpdate = () => {
            var _a;
            const scrollElement = this.options.enabled ? this.options.getScrollElement() : null;
            if (this.scrollElement !== scrollElement) {
                this.cleanup();
                if (!scrollElement) {
                    this.maybeNotify();
                    return;
                }
                this.scrollElement = scrollElement;
                if (this.scrollElement && "ownerDocument"in this.scrollElement) {
                    this.targetWindow = this.scrollElement.ownerDocument.defaultView;
                } else {
                    this.targetWindow = ((_a = this.scrollElement) == null ? void 0 : _a.window) ?? null;
                }
                this.elementsCache.forEach( (cached) => {
                    this.observer.observe(cached);
                }
                );
                this._scrollToOffset(this.getScrollOffset(), {
                    adjustments: void 0,
                    behavior: void 0
                });
                this.unsubs.push(this.options.observeElementRect(this, (rect) => {
                    this.scrollRect = rect;
                    this.maybeNotify();
                }
                ));
                this.unsubs.push(this.options.observeElementOffset(this, (offset, isScrolling) => {
                    this.scrollAdjustments = 0;
                    this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null;
                    this.scrollOffset = offset;
                    this.isScrolling = isScrolling;
                    this.maybeNotify();
                }
                ));
            }
        }
        ;
        this.getSize = () => {
            if (!this.options.enabled) {
                this.scrollRect = null;
                return 0;
            }
            this.scrollRect = this.scrollRect ?? this.options.initialRect;
            return this.scrollRect[this.options.horizontal ? "width" : "height"];
        }
        ;
        this.getScrollOffset = () => {
            if (!this.options.enabled) {
                this.scrollOffset = null;
                return 0;
            }
            this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset);
            return this.scrollOffset;
        }
        ;
        this.getFurthestMeasurement = (measurements, index) => {
            const furthestMeasurementsFound = /* @__PURE__ */
            new Map();
            const furthestMeasurements = /* @__PURE__ */
            new Map();
            for (let m = index - 1; m >= 0; m--) {
                const measurement = measurements[m];
                if (furthestMeasurementsFound.has(measurement.lane)) {
                    continue;
                }
                const previousFurthestMeasurement = furthestMeasurements.get(measurement.lane);
                if (previousFurthestMeasurement == null || measurement.end > previousFurthestMeasurement.end) {
                    furthestMeasurements.set(measurement.lane, measurement);
                } else if (measurement.end < previousFurthestMeasurement.end) {
                    furthestMeasurementsFound.set(measurement.lane, true);
                }
                if (furthestMeasurementsFound.size === this.options.lanes) {
                    break;
                }
            }
            return furthestMeasurements.size === this.options.lanes ? Array.from(furthestMeasurements.values()).sort( (a, b) => {
                if (a.end === b.end) {
                    return a.index - b.index;
                }
                return a.end - b.end;
            }
            )[0] : void 0;
        }
        ;
        this.getMeasurementOptions = (0,
        _utils_js__WEBPACK_IMPORTED_MODULE_0__.memo)( () => [this.options.count, this.options.paddingStart, this.options.scrollMargin, this.options.getItemKey, this.options.enabled], (count, paddingStart, scrollMargin, getItemKey, enabled) => {
            this.pendingMeasuredCacheIndexes = [];
            return {
                count,
                paddingStart,
                scrollMargin,
                getItemKey,
                enabled
            };
        }
        , {
            key: false
        });
        this.getMeasurements = (0,
        _utils_js__WEBPACK_IMPORTED_MODULE_0__.memo)( () => [this.getMeasurementOptions(), this.itemSizeCache], ({count, paddingStart, scrollMargin, getItemKey, enabled}, itemSizeCache) => {
            if (!enabled) {
                this.measurementsCache = [];
                this.itemSizeCache.clear();
                return [];
            }
            if (this.measurementsCache.length === 0) {
                this.measurementsCache = this.options.initialMeasurementsCache;
                this.measurementsCache.forEach( (item) => {
                    this.itemSizeCache.set(item.key, item.size);
                }
                );
            }
            const min = this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
            this.pendingMeasuredCacheIndexes = [];
            const measurements = this.measurementsCache.slice(0, min);
            for (let i = min; i < count; i++) {
                const key = getItemKey(i);
                const furthestMeasurement = this.options.lanes === 1 ? measurements[i - 1] : this.getFurthestMeasurement(measurements, i);
                const start = furthestMeasurement ? furthestMeasurement.end + this.options.gap : paddingStart + scrollMargin;
                const measuredSize = itemSizeCache.get(key);
                const size = typeof measuredSize === "number" ? measuredSize : this.options.estimateSize(i);
                const end = start + size;
                const lane = furthestMeasurement ? furthestMeasurement.lane : i % this.options.lanes;
                measurements[i] = {
                    index: i,
                    start,
                    size,
                    end,
                    key,
                    lane
                };
            }
            this.measurementsCache = measurements;
            return measurements;
        }
        , {
            key: true && "getMeasurements",
            debug: () => this.options.debug
        });
        this.calculateRange = (0,
        _utils_js__WEBPACK_IMPORTED_MODULE_0__.memo)( () => [this.getMeasurements(), this.getSize(), this.getScrollOffset(), this.options.lanes], (measurements, outerSize, scrollOffset, lanes) => {
            return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({
                measurements,
                outerSize,
                scrollOffset,
                lanes
            }) : null;
        }
        , {
            key: true && "calculateRange",
            debug: () => this.options.debug
        });
        this.getVirtualIndexes = (0,
        _utils_js__WEBPACK_IMPORTED_MODULE_0__.memo)( () => {
            let startIndex = null;
            let endIndex = null;
            const range = this.calculateRange();
            if (range) {
                startIndex = range.startIndex;
                endIndex = range.endIndex;
            }
            this.maybeNotify.updateDeps([this.isScrolling, startIndex, endIndex]);
            return [this.options.rangeExtractor, this.options.overscan, this.options.count, startIndex, endIndex];
        }
        , (rangeExtractor, overscan, count, startIndex, endIndex) => {
            return startIndex === null || endIndex === null ? [] : rangeExtractor({
                startIndex,
                endIndex,
                overscan,
                count
            });
        }
        , {
            key: true && "getVirtualIndexes",
            debug: () => this.options.debug
        });
        this.indexFromElement = (node) => {
            const attributeName = this.options.indexAttribute;
            const indexStr = node.getAttribute(attributeName);
            if (!indexStr) {
                console.warn(`Missing attribute name '${attributeName}={index}' on measured element.`);
                return -1;
            }
            return parseInt(indexStr, 10);
        }
        ;
        this._measureElement = (node, entry) => {
            const index = this.indexFromElement(node);
            const item = this.measurementsCache[index];
            if (!item) {
                return;
            }
            const key = item.key;
            const prevNode = this.elementsCache.get(key);
            if (prevNode !== node) {
                if (prevNode) {
                    this.observer.unobserve(prevNode);
                }
                this.observer.observe(node);
                this.elementsCache.set(key, node);
            }
            if (node.isConnected) {
                this.resizeItem(index, this.options.measureElement(node, entry, this));
            }
        }
        ;
        this.resizeItem = (index, size) => {
            const item = this.measurementsCache[index];
            if (!item) {
                return;
            }
            const itemSize = this.itemSizeCache.get(item.key) ?? item.size;
            const delta = size - itemSize;
            if (delta !== 0) {
                if (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments) {
                    if (true && this.options.debug) {
                        console.info("correction", delta);
                    }
                    this._scrollToOffset(this.getScrollOffset(), {
                        adjustments: this.scrollAdjustments += delta,
                        behavior: void 0
                    });
                }
                this.pendingMeasuredCacheIndexes.push(item.index);
                this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size));
                this.notify(false);
            }
        }
        ;
        this.measureElement = (node) => {
            if (!node) {
                this.elementsCache.forEach( (cached, key) => {
                    if (!cached.isConnected) {
                        this.observer.unobserve(cached);
                        this.elementsCache.delete(key);
                    }
                }
                );
                return;
            }
            this._measureElement(node, void 0);
        }
        ;
        this.getVirtualItems = (0,
        _utils_js__WEBPACK_IMPORTED_MODULE_0__.memo)( () => [this.getVirtualIndexes(), this.getMeasurements()], (indexes, measurements) => {
            const virtualItems = [];
            for (let k = 0, len = indexes.length; k < len; k++) {
                const i = indexes[k];
                const measurement = measurements[i];
                virtualItems.push(measurement);
            }
            return virtualItems;
        }
        , {
            key: true && "getVirtualItems",
            debug: () => this.options.debug
        });
        this.getVirtualItemForOffset = (offset) => {
            const measurements = this.getMeasurements();
            if (measurements.length === 0) {
                return void 0;
            }
            return (0,
            _utils_js__WEBPACK_IMPORTED_MODULE_0__.notUndefined)(measurements[findNearestBinarySearch(0, measurements.length - 1, (index) => (0,
            _utils_js__WEBPACK_IMPORTED_MODULE_0__.notUndefined)(measurements[index]).start, offset)]);
        }
        ;
        this.getOffsetForAlignment = (toOffset, align, itemSize=0) => {
            const size = this.getSize();
            const scrollOffset = this.getScrollOffset();
            if (align === "auto") {
                align = toOffset >= scrollOffset + size ? "end" : "start";
            }
            if (align === "center") {
                toOffset += (itemSize - size) / 2;
            } else if (align === "end") {
                toOffset -= size;
            }
            const maxOffset = this.getTotalSize() + this.options.scrollMargin - size;
            return Math.max(Math.min(maxOffset, toOffset), 0);
        }
        ;
        this.getOffsetForIndex = (index, align="auto") => {
            index = Math.max(0, Math.min(index, this.options.count - 1));
            const item = this.measurementsCache[index];
            if (!item) {
                return void 0;
            }
            const size = this.getSize();
            const scrollOffset = this.getScrollOffset();
            if (align === "auto") {
                if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) {
                    align = "end";
                } else if (item.start <= scrollOffset + this.options.scrollPaddingStart) {
                    align = "start";
                } else {
                    return [scrollOffset, align];
                }
            }
            const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart;
            return [this.getOffsetForAlignment(toOffset, align, item.size), align];
        }
        ;
        this.isDynamicMode = () => this.elementsCache.size > 0;
        this.scrollToOffset = (toOffset, {align="start", behavior}={}) => {
            if (behavior === "smooth" && this.isDynamicMode()) {
                console.warn("The `smooth` scroll behavior is not fully supported with dynamic size.");
            }
            this._scrollToOffset(this.getOffsetForAlignment(toOffset, align), {
                adjustments: void 0,
                behavior
            });
        }
        ;
        this.scrollToIndex = (index, {align: initialAlign="auto", behavior}={}) => {
            if (behavior === "smooth" && this.isDynamicMode()) {
                console.warn("The `smooth` scroll behavior is not fully supported with dynamic size.");
            }
            index = Math.max(0, Math.min(index, this.options.count - 1));
            let attempts = 0;
            const maxAttempts = 10;
            const tryScroll = (currentAlign) => {
                if (!this.targetWindow)
                    return;
                const offsetInfo = this.getOffsetForIndex(index, currentAlign);
                if (!offsetInfo) {
                    console.warn("Failed to get offset for index:", index);
                    return;
                }
                const [offset,align] = offsetInfo;
                this._scrollToOffset(offset, {
                    adjustments: void 0,
                    behavior
                });
                this.targetWindow.requestAnimationFrame( () => {
                    const currentOffset = this.getScrollOffset();
                    const afterInfo = this.getOffsetForIndex(index, align);
                    if (!afterInfo) {
                        console.warn("Failed to get offset for index:", index);
                        return;
                    }
                    if (!(0,
                    _utils_js__WEBPACK_IMPORTED_MODULE_0__.approxEqual)(afterInfo[0], currentOffset)) {
                        scheduleRetry(align);
                    }
                }
                );
            }
            ;
            const scheduleRetry = (align) => {
                if (!this.targetWindow)
                    return;
                attempts++;
                if (attempts < maxAttempts) {
                    if (true && this.options.debug) {
                        console.info("Schedule retry", attempts, maxAttempts);
                    }
                    this.targetWindow.requestAnimationFrame( () => tryScroll(align));
                } else {
                    console.warn(`Failed to scroll to index ${index} after ${maxAttempts} attempts.`);
                }
            }
            ;
            tryScroll(initialAlign);
        }
        ;
        this.scrollBy = (delta, {behavior}={}) => {
            if (behavior === "smooth" && this.isDynamicMode()) {
                console.warn("The `smooth` scroll behavior is not fully supported with dynamic size.");
            }
            this._scrollToOffset(this.getScrollOffset() + delta, {
                adjustments: void 0,
                behavior
            });
        }
        ;
        this.getTotalSize = () => {
            var _a;
            const measurements = this.getMeasurements();
            let end;
            if (measurements.length === 0) {
                end = this.options.paddingStart;
            } else if (this.options.lanes === 1) {
                end = ((_a = measurements[measurements.length - 1]) == null ? void 0 : _a.end) ?? 0;
            } else {
                const endByLane = Array(this.options.lanes).fill(null);
                let endIndex = measurements.length - 1;
                while (endIndex >= 0 && endByLane.some( (val) => val === null)) {
                    const item = measurements[endIndex];
                    if (endByLane[item.lane] === null) {
                        endByLane[item.lane] = item.end;
                    }
                    endIndex--;
                }
                end = Math.max(...endByLane.filter( (val) => val !== null));
            }
            return Math.max(end - this.options.scrollMargin + this.options.paddingEnd, 0);
        }
        ;
        this._scrollToOffset = (offset, {adjustments, behavior}) => {
            this.options.scrollToFn(offset, {
                behavior,
                adjustments
            }, this);
        }
        ;
        this.measure = () => {
            this.itemSizeCache = /* @__PURE__ */
            new Map();
            this.notify(false);
        }
        ;
        this.setOptions(opts);
    }
}
const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
    while (low <= high) {
        const middle = (low + high) / 2 | 0;
        const currentValue = getCurrentValue(middle);
        if (currentValue < value) {
            low = middle + 1;
        } else if (currentValue > value) {
            high = middle - 1;
        } else {
            return middle;
        }
    }
    if (low > 0) {
        return low - 1;
    } else {
        return 0;
    }
}
;
function calculateRange({measurements, outerSize, scrollOffset, lanes}) {
    const lastIndex = measurements.length - 1;
    const getOffset = (index) => measurements[index].start;
    if (measurements.length <= lanes) {
        return {
            startIndex: 0,
            endIndex: lastIndex
        };
    }
    let startIndex = findNearestBinarySearch(0, lastIndex, getOffset, scrollOffset);
    let endIndex = startIndex;
    if (lanes === 1) {
        while (endIndex < lastIndex && measurements[endIndex].end < scrollOffset + outerSize) {
            endIndex++;
        }
    } else if (lanes > 1) {
        const endPerLane = Array(lanes).fill(0);
        while (endIndex < lastIndex && endPerLane.some( (pos) => pos < scrollOffset + outerSize)) {
            const item = measurements[endIndex];
            endPerLane[item.lane] = item.end;
            endIndex++;
        }
        const startPerLane = Array(lanes).fill(scrollOffset + outerSize);
        while (startIndex >= 0 && startPerLane.some( (pos) => pos >= scrollOffset)) {
            const item = measurements[startIndex];
            startPerLane[item.lane] = item.start;
            startIndex--;
        }
        startIndex = Math.max(0, startIndex - startIndex % lanes);
        endIndex = Math.min(lastIndex, endIndex + (lanes - 1 - endIndex % lanes));
    }
    return {
        startIndex,
        endIndex
    };
}
Your minimal, reproducible example

Note: I did not provide an onChange prop to the Virtualizer. The infinite update loop is triggered when the outer container’s dimensions change (for example, when resizing the browser window or the parent element).

Steps to reproduce

Note: I did not provide an onChange prop to the Virtualizer.
The infinite update loop is triggered when the outer container’s dimensions change (for example, when resizing the browser window or the parent element).

Expected behavior

list

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

web

tanstack-virtual version

latest

TypeScript version

No response

Additional context

No response

Terms & Code of Conduct
  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
主要言語
TypeScript
スター
7.1k
フォーク
472
平均マージ
1日 2時間
マージ済み PR(30日)
12

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

TanStack/virtual のほかの issue

TanStack/virtual の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。