🐛 Bug Report: “Maximum update depth exceeded” when using onChange with Virtualizer
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 45/100
- Tipo di issue
- Bug
- Chiarezza
- Da chiarire
- Stato di attività
- Tranquilla
- Stack tecnologico
- react, typescript
- Ambito
- frontend, performance
Direzione di ricerca
Inizia dalla configurazione useVirtualizer segnalata e dallo stack delle chiamate attraverso Virtualizer.notify, resizeItem e _measureElement; riproduci il problema modificando le dimensioni del contenitore esterno. Il report non indica alcun file sorgente o test del repository, quindi individua prima l'implementazione del virtualizer React e i test di ridimensionamento esistenti. Il lavoro è completato quando il ridimensionamento non produce un loop di maximum-update-depth e il normale comportamento di virtualizzazione rimane intatto.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- TypeScript
- Stelle
- 7.1k
- Fork
- 472
- Merge medio
- 1g 2h
- PR unite (30g)
- 12
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/virtual
-
measureElement caches 0 size for nodes that aren't attached yet (Vue: rows stack at start 0 and the list scrolls away on mount)Forse già presa @webn00b l’ha presa 10 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
`scrollToIndex(last, { align: 'end' })` stays short of the end after a measured row grows (virtual-core ≥ 3.17.0)Forse già presa @minwookshin l’ha presa 7 giorni fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
TanStack/virtual#1290 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
virtual-core: desktop Safari drops the prepend anchor write when history lands during the top rubber-band bounceForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 38/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
marko-virtual: scrollToEnd during an in-flight prepend strands the view one prepend above the bottom (chat-pretext e2e fails on CI)Forse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di TanStack/virtual
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
Comfy-Org/ComfyUI_frontend#20346 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
decentralized-identity/didwebvh-ts#203 ·
I maintainer di solito rispondono entro 1 giorno
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
lingdojo/kana-dojo#31791 · 1 commento · 5 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
Telegram webhook: line breaks lost since switch to rich messagesForse già presa @Kshot3000 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
github_actions security
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno