The @tanstack/react-virtual adapter is a wrapper around the core virtual logic.
function useVirtualizer<TScrollElement, TItemElement = unknown>(
options: PartialKeys<
ReactVirtualizerOptions<TScrollElement, TItemElement>,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer<TScrollElement, TItemElement>This function returns a standard Virtualizer instance configured to work with an HTML element as the scrollElement.
function useWindowVirtualizer<TItemElement = unknown>(
options: PartialKeys<
ReactVirtualizerOptions<Window, TItemElement>,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer<Window, TItemElement>This function returns a window-based Virtualizer instance configured to work with the window as the scrollElement.
type ReactVirtualizerOptions<TScrollElement, TItemElement> =
VirtualizerOptions<TScrollElement, TItemElement> & {
useFlushSync?: boolean
}Both useVirtualizer and useWindowVirtualizer accept a useFlushSync option that controls whether React's flushSync is used for synchronous updates.
Type: boolean
Default: true
Description: When true, the virtualizer will use flushSync from react-dom to ensure synchronous rendering during scroll events. This provides the most accurate scrolling behavior but may impact performance in some scenarios.
You may want to set useFlushSync: false in the following scenarios:
React 19 compatibility: In React 19, you may see the following console warning when scrolling:
flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.Setting useFlushSync: false will eliminate this warning by allowing React to batch updates naturally.
Performance optimization: If you experience performance issues with rapid scrolling on lower-end devices
Testing environments: When running tests that don't require synchronous DOM updates
Non-critical lists: When slight visual delays during scrolling are acceptable for better overall performance
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
useFlushSync: false, // Disable synchronous updates
})Type: boolean
Default: false
Description: Skip React re-renders for scroll-only updates. When enabled, the virtualizer writes item positions (top/left or transform) and the container size (height/width) directly to the DOM, and only re-renders when the visible index range or isScrolling changes.
Item elements must be position: absolute; in 'transform' mode they must also be anchored with top: 0 / left: 0.
Item elements must not set the main-axis position in their style — the virtualizer owns top / left in 'position' mode and transform in 'transform' mode.
The inner size container must receive virtualizer.containerRef and must not set height / width in its style.
For multi-lane layouts (grids / masonry), the cross-axis position (e.g. left: ${(item.lane * 100) / lanes}%) is stable per item and must still be set in your JSX — only the main axis is automated.
⚠️ This flag is intended to be set once at mount. Toggling it (or directDomUpdatesMode) at runtime can leave stale inline styles on items and the container.
Note: If you omit containerRef, the virtualizer makes no direct DOM writes — it writes neither item positions nor the container size. You're then responsible for positioning items and sizing the container yourself (e.g. in onChange), while still benefiting from the skipped re-renders.
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
directDomUpdates: true,
})
return (
<div ref={parentRef} style={{ overflow: 'auto', height: 400 }}>
{/* The inner container must use virtualizer.containerRef and not set height */}
<div ref={virtualizer.containerRef} style={{ position: 'relative' }}>
{virtualizer.getVirtualItems().map((item) => (
<div
key={item.key}
ref={virtualizer.measureElement}
data-index={item.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
// Do NOT set top/left/transform — the virtualizer handles it
}}
>
Row {item.index}
</div>
))}
</div>
</div>
)Type: 'position' | 'transform'
Default: 'transform'
Description: Controls how directDomUpdates positions item elements.
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
directDomUpdates: true,
directDomUpdatesMode: 'position', // Use top/left instead of transform
})