0
# React Virtualization Hooks
1
2
React-specific hooks that provide virtualized scrolling for elements and windows with automatic state management and optimal re-rendering performance.
3
4
## Capabilities
5
6
### useVirtualizer Hook
7
8
Creates a virtualizer instance for element-based scrolling with automatic React integration and state management.
9
10
```typescript { .api }
11
/**
12
* React hook for element-based virtualization
13
* @param options - Virtualization configuration with element-specific defaults
14
* @returns Virtualizer instance with React integration
15
*/
16
export function useVirtualizer<TScrollElement extends Element, TItemElement extends Element>(
17
options: PartialKeys<
18
VirtualizerOptions<TScrollElement, TItemElement>,
19
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
20
>
21
): Virtualizer<TScrollElement, TItemElement>;
22
```
23
24
The hook automatically provides default implementations for:
25
- `observeElementRect`: Uses ResizeObserver for element size changes
26
- `observeElementOffset`: Monitors scroll position changes
27
- `scrollToFn`: Handles scrolling with element.scrollTo()
28
29
**Usage Examples:**
30
31
```typescript
32
import React from 'react';
33
import { useVirtualizer } from '@tanstack/react-virtual';
34
35
// Basic list virtualization
36
function BasicList({ items }: { items: string[] }) {
37
const parentRef = React.useRef<HTMLDivElement>(null);
38
39
const virtualizer = useVirtualizer({
40
count: items.length,
41
getScrollElement: () => parentRef.current,
42
estimateSize: () => 35,
43
});
44
45
return (
46
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
47
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
48
{virtualizer.getVirtualItems().map((virtualItem) => (
49
<div
50
key={virtualItem.key}
51
data-index={virtualItem.index}
52
style={{
53
position: 'absolute',
54
top: 0,
55
left: 0,
56
width: '100%',
57
height: `${virtualItem.size}px`,
58
transform: `translateY(${virtualItem.start}px)`,
59
}}
60
>
61
{items[virtualItem.index]}
62
</div>
63
))}
64
</div>
65
</div>
66
);
67
}
68
69
// Horizontal scrolling
70
function HorizontalList({ items }: { items: any[] }) {
71
const parentRef = React.useRef<HTMLDivElement>(null);
72
73
const virtualizer = useVirtualizer({
74
count: items.length,
75
getScrollElement: () => parentRef.current,
76
estimateSize: () => 120,
77
horizontal: true,
78
});
79
80
return (
81
<div ref={parentRef} style={{ width: '400px', overflowX: 'auto' }}>
82
<div style={{ width: `${virtualizer.getTotalSize()}px`, height: '100px', position: 'relative' }}>
83
{virtualizer.getVirtualItems().map((virtualItem) => (
84
<div
85
key={virtualItem.key}
86
data-index={virtualItem.index}
87
style={{
88
position: 'absolute',
89
top: 0,
90
left: 0,
91
height: '100%',
92
width: `${virtualItem.size}px`,
93
transform: `translateX(${virtualItem.start}px)`,
94
}}
95
>
96
Item {virtualItem.index}
97
</div>
98
))}
99
</div>
100
</div>
101
);
102
}
103
104
// Dynamic sizing with measurement
105
function DynamicList({ items }: { items: { content: string; height?: number }[] }) {
106
const parentRef = React.useRef<HTMLDivElement>(null);
107
108
const virtualizer = useVirtualizer({
109
count: items.length,
110
getScrollElement: () => parentRef.current,
111
estimateSize: (index) => items[index]?.height ?? 50,
112
overscan: 3,
113
});
114
115
return (
116
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
117
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
118
{virtualizer.getVirtualItems().map((virtualItem) => (
119
<div
120
key={virtualItem.key}
121
data-index={virtualItem.index}
122
ref={(node) => virtualizer.measureElement(node)}
123
style={{
124
position: 'absolute',
125
top: 0,
126
left: 0,
127
width: '100%',
128
transform: `translateY(${virtualItem.start}px)`,
129
}}
130
>
131
<div style={{ padding: '8px' }}>
132
{items[virtualItem.index]?.content}
133
</div>
134
</div>
135
))}
136
</div>
137
</div>
138
);
139
}
140
```
141
142
### useWindowVirtualizer Hook
143
144
Creates a virtualizer instance for window-based scrolling, ideal for full-page virtualization scenarios.
145
146
```typescript { .api }
147
/**
148
* React hook for window-based virtualization
149
* @param options - Virtualization configuration with window-specific defaults
150
* @returns Virtualizer instance configured for window scrolling
151
*/
152
export function useWindowVirtualizer<TItemElement extends Element>(
153
options: PartialKeys<
154
VirtualizerOptions<Window, TItemElement>,
155
| 'getScrollElement'
156
| 'observeElementRect'
157
| 'observeElementOffset'
158
| 'scrollToFn'
159
>
160
): Virtualizer<Window, TItemElement>;
161
```
162
163
The hook automatically provides default implementations for:
164
- `getScrollElement`: Returns the window object
165
- `observeElementRect`: Uses window resize events
166
- `observeElementOffset`: Monitors window scroll position
167
- `scrollToFn`: Handles scrolling with window.scrollTo()
168
- `initialOffset`: Uses current window.scrollY position
169
170
**Usage Examples:**
171
172
```typescript
173
import React from 'react';
174
import { useWindowVirtualizer } from '@tanstack/react-virtual';
175
176
// Full-page virtualization
177
function WindowVirtualizedList({ items }: { items: string[] }) {
178
const virtualizer = useWindowVirtualizer({
179
count: items.length,
180
estimateSize: () => 100,
181
overscan: 5,
182
});
183
184
return (
185
<div style={{ paddingTop: '200px' }}>
186
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
187
{virtualizer.getVirtualItems().map((virtualItem) => (
188
<div
189
key={virtualItem.key}
190
data-index={virtualItem.index}
191
style={{
192
position: 'absolute',
193
top: 0,
194
left: 0,
195
width: '100%',
196
height: `${virtualItem.size}px`,
197
transform: `translateY(${virtualItem.start}px)`,
198
}}
199
>
200
<div style={{ padding: '16px', border: '1px solid #ccc' }}>
201
{items[virtualItem.index]}
202
</div>
203
</div>
204
))}
205
</div>
206
</div>
207
);
208
}
209
210
// Infinite scroll with window virtualization
211
function InfiniteScrollList() {
212
const [items, setItems] = React.useState<string[]>(
213
Array.from({ length: 100 }, (_, i) => `Item ${i}`)
214
);
215
const [isLoading, setIsLoading] = React.useState(false);
216
217
const virtualizer = useWindowVirtualizer({
218
count: items.length,
219
estimateSize: () => 80,
220
overscan: 10,
221
});
222
223
const virtualItems = virtualizer.getVirtualItems();
224
const lastItem = virtualItems[virtualItems.length - 1];
225
226
React.useEffect(() => {
227
if (!lastItem || isLoading) return;
228
229
if (lastItem.index >= items.length - 1) {
230
setIsLoading(true);
231
// Simulate loading more items
232
setTimeout(() => {
233
setItems(prev => [
234
...prev,
235
...Array.from({ length: 50 }, (_, i) => `Item ${prev.length + i}`)
236
]);
237
setIsLoading(false);
238
}, 1000);
239
}
240
}, [lastItem?.index, items.length, isLoading]);
241
242
return (
243
<div>
244
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
245
{virtualItems.map((virtualItem) => (
246
<div
247
key={virtualItem.key}
248
data-index={virtualItem.index}
249
style={{
250
position: 'absolute',
251
top: 0,
252
left: 0,
253
width: '100%',
254
height: `${virtualItem.size}px`,
255
transform: `translateY(${virtualItem.start}px)`,
256
padding: '8px',
257
borderBottom: '1px solid #eee',
258
}}
259
>
260
{items[virtualItem.index]}
261
</div>
262
))}
263
</div>
264
{isLoading && <div style={{ padding: '20px', textAlign: 'center' }}>Loading...</div>}
265
</div>
266
);
267
}
268
```
269
270
## Configuration Options
271
272
Both hooks accept the same base `VirtualizerOptions` with certain defaults provided:
273
274
### Required Options
275
276
```typescript { .api }
277
export interface RequiredVirtualizerOptions {
278
/** Total number of items to virtualize */
279
count: number;
280
/** Function to estimate the size of each item */
281
estimateSize: (index: number) => number;
282
}
283
```
284
285
### Common Optional Options
286
287
```typescript { .api }
288
export interface CommonVirtualizerOptions {
289
/** Enable horizontal scrolling instead of vertical */
290
horizontal?: boolean;
291
/** Number of items to render outside the visible area */
292
overscan?: number;
293
/** Padding at the start of the scroll area */
294
paddingStart?: number;
295
/** Padding at the end of the scroll area */
296
paddingEnd?: number;
297
/** Number of lanes for grid layout */
298
lanes?: number;
299
/** Gap between items in pixels */
300
gap?: number;
301
/** Enable debug logging */
302
debug?: boolean;
303
/** Enable right-to-left layout */
304
isRtl?: boolean;
305
/** Custom function to extract keys from indices */
306
getItemKey?: (index: number) => Key;
307
/** Custom function to extract visible range */
308
rangeExtractor?: (range: Range) => Array<number>;
309
/** Custom element measurement function */
310
measureElement?: (element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, TItemElement>) => number;
311
}
312
```
313
314
## Performance Considerations
315
316
### Optimal Re-rendering
317
318
Both hooks use React's state management to trigger re-renders only when necessary:
319
320
- Automatic batching of scroll updates using `flushSync` for smooth scrolling
321
- Memoized calculations to prevent unnecessary recalculations
322
- Efficient range calculation to minimize DOM operations
323
324
### Memory Management
325
326
- Virtual items are created and destroyed as needed
327
- Element references are automatically cleaned up
328
- ResizeObserver connections are managed automatically
329
330
### Best Practices
331
332
1. **Use stable estimateSize functions**: Avoid creating new functions on every render
333
2. **Implement proper key extraction**: Use `getItemKey` for items with stable identifiers
334
3. **Optimize item rendering**: Use React.memo for item components when appropriate
335
4. **Handle dynamic content**: Use `measureElement` ref callback for accurate sizing of dynamic content