3.1 KiB
Virtual Scrolling Debug Notes
Date: 2026-04-03
Issue: Virtual Scrolling Not Updating on Scroll
Symptoms
- Rows weren't shifting when scrolling programmatically
- Debug info showed correct scrollTop but rows stayed the same
- Force re-render (classList toggle) fixed it momentarily
Root Causes Found
1. Vue Template Ref Auto-Unwrapping
Problem: virtualizer.visibleRows is a ComputedRef inside an object. Vue 3 auto-unwraps top-level refs in templates, but NOT refs nested inside objects.
Evidence:
TypeScript error: Property 'index' does not exist on type 'true | ComputedRefImpl<any> | VirtualRow[]'
Fix: Destructure the needed values from virtualizer:
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;
2. Vue 3 Template .value Usage
Problem: In Vue 3 <script setup>, refs are auto-unwrapped in templates. Using .value explicitly on refs returned from a hook (inside an object) causes type inference issues.
Before (broken):
<div v-for="row in virtualizer.visibleRows.value">
After (working):
<div v-for="row in visibleRows">
3. Overscan + Small Dataset
Problem: With 10 rows (each ~60px = 600px total) and viewport 306px, overscan=5 means all 10 rows are rendered regardless of scroll position.
Calculation:
- Viewport shows ~5 rows
- Overscan 5 = render rows -5 to +5 from viewport edge
- 5 + 5 + visible rows = all 10 rows always visible
Not a bug - virtual scrolling only benefits when rows > viewport + overscan.
Key Code Changes
list-table-v2.vue
// Before: all refs nested in object
const virtualizer = useVirtualRows(...);
// After: destructure for template auto-unwrap
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;
useVirtualRows.ts
// Added safety guard for invalid indices
const visibleRows = computed<VirtualRow[]>(() => {
if (startIndex < 0 || endIndex < startIndex) {
return [];
}
// ... rest of computation
});
Debug Techniques Used
-
Force re-render:
table.classList.add('x'); requestAnimationFrame(() => table.classList.remove('x'))- If rows change after force-re-render, reactivity IS working but scroll event isn't triggering updates
-
Check virtualizer state: Debug info showed
Visible Rows: 10andTotal Rows: 10- all rows were intentionally rendered -
Calculate expected behavior:
- viewport = 306px
- total content = 660px (10 rows)
- max scroll = 354px
- overscan = 5 on each side
Lessons Learned
- In Vue 3
<script setup>, always destructure refs that need to be used in templates - Virtual scrolling benefit is only visible when
total_rows > viewport + (overscan * row_height) - Vue 3 auto-unwraps top-level refs but NOT refs nested in objects
console.login Vue event handlers doesn't appear in browser console - Vue intercepts events- Programmatic scroll (
scrollTop = x) doesn't fire scroll events - need to manually dispatch
Files Modified
packages/base/data/list-table-v2/list-table-v2.vuepackages/base/data/list-table-v2/useVirtualRows.ts