0
# Virtual Scrolling
1
2
RC Tree provides performance optimization for large tree datasets using virtual scrolling with configurable item heights and scroll behaviors, powered by rc-virtual-list.
3
4
## Capabilities
5
6
### Virtual Scrolling Configuration
7
8
Enable virtual scrolling to handle large trees efficiently by only rendering visible nodes.
9
10
```typescript { .api }
11
/**
12
* Virtual scrolling configuration options
13
*/
14
interface VirtualScrollConfig {
15
/** Enable virtual scrolling */
16
virtual?: boolean;
17
/** Fixed height of the tree container */
18
height?: number;
19
/** Fixed height of individual tree items */
20
itemHeight?: number;
21
/** Width of the scroll container */
22
scrollWidth?: number;
23
/** Scroll offset per item */
24
itemScrollOffset?: number;
25
}
26
27
/**
28
* Scroll control interface from rc-virtual-list
29
*/
30
interface ScrollTo {
31
/** Scroll to specific index */
32
(index?: number): void;
33
/** Scroll to specific index with alignment */
34
(index: number, align: 'top' | 'bottom' | 'auto'): void;
35
}
36
```
37
38
**Usage Examples:**
39
40
### Basic Virtual Scrolling
41
42
```typescript
43
import React, { useState, useMemo } from "react";
44
import Tree from "rc-tree";
45
46
const BasicVirtualScrolling = () => {
47
// Generate large dataset
48
const treeData = useMemo(() => {
49
const generateData = (level: number, parentKey: string, count: number): any[] => {
50
return Array.from({ length: count }, (_, index) => {
51
const key = `${parentKey}-${index}`;
52
const title = `Node ${key}`;
53
54
if (level > 0) {
55
return {
56
key,
57
title,
58
children: generateData(level - 1, key, 5),
59
};
60
}
61
62
return { key, title, isLeaf: true };
63
});
64
};
65
66
return generateData(3, '0', 100); // 100 root nodes, each with nested children
67
}, []);
68
69
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
70
71
return (
72
<div>
73
<h3>Virtual Scrolling Tree ({treeData.length} root nodes)</h3>
74
<Tree
75
prefixCls="rc-tree"
76
virtual
77
height={400} // Fixed height container
78
itemHeight={24} // Fixed height per item
79
treeData={treeData}
80
expandedKeys={expandedKeys}
81
onExpand={setExpandedKeys}
82
defaultExpandParent={false}
83
/>
84
</div>
85
);
86
};
87
```
88
89
### Virtual Scrolling with Dynamic Content
90
91
```typescript
92
import React, { useState, useMemo } from "react";
93
import Tree from "rc-tree";
94
95
interface VirtualNodeData {
96
key: string;
97
title: string;
98
description?: string;
99
children?: VirtualNodeData[];
100
}
101
102
const DynamicVirtualScrolling = () => {
103
const [searchTerm, setSearchTerm] = useState('');
104
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
105
106
// Generate large dataset with searchable content
107
const allData = useMemo(() => {
108
const generateNode = (id: number, parentPath: string = ''): VirtualNodeData => {
109
const key = parentPath ? `${parentPath}-${id}` : `${id}`;
110
return {
111
key,
112
title: `Item ${key}`,
113
description: `Description for item ${key} with some searchable content`,
114
children: id < 1000 ? Array.from({ length: 3 }, (_, i) =>
115
generateNode(i, key)
116
) : undefined,
117
};
118
};
119
120
return Array.from({ length: 500 }, (_, i) => generateNode(i));
121
}, []);
122
123
// Filter data based on search term
124
const filteredData = useMemo(() => {
125
if (!searchTerm) return allData;
126
127
const filterNodes = (nodes: VirtualNodeData[]): VirtualNodeData[] => {
128
return nodes.reduce((acc, node) => {
129
const matchesSearch = node.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
130
node.description?.toLowerCase().includes(searchTerm.toLowerCase());
131
132
let filteredChildren: VirtualNodeData[] = [];
133
if (node.children) {
134
filteredChildren = filterNodes(node.children);
135
}
136
137
if (matchesSearch || filteredChildren.length > 0) {
138
acc.push({
139
...node,
140
children: filteredChildren.length > 0 ? filteredChildren : node.children,
141
});
142
}
143
144
return acc;
145
}, [] as VirtualNodeData[]);
146
};
147
148
return filterNodes(allData);
149
}, [allData, searchTerm]);
150
151
const titleRender = (node: VirtualNodeData) => (
152
<div>
153
<strong>{node.title}</strong>
154
{node.description && (
155
<div style={{ fontSize: '0.8em', color: '#666' }}>
156
{node.description}
157
</div>
158
)}
159
</div>
160
);
161
162
return (
163
<div>
164
<div style={{ marginBottom: 16 }}>
165
<input
166
type="text"
167
placeholder="Search nodes..."
168
value={searchTerm}
169
onChange={(e) => setSearchTerm(e.target.value)}
170
style={{ width: '100%', padding: 8 }}
171
/>
172
<p>Showing {filteredData.length} items</p>
173
</div>
174
175
<Tree
176
prefixCls="rc-tree"
177
virtual
178
height={500}
179
itemHeight={48} // Taller items for two-line content
180
treeData={filteredData}
181
titleRender={titleRender}
182
expandedKeys={expandedKeys}
183
onExpand={setExpandedKeys}
184
defaultExpandParent={false}
185
/>
186
</div>
187
);
188
};
189
```
190
191
### Virtual Scrolling with Checkboxes and Selection
192
193
```typescript
194
import React, { useState, useMemo } from "react";
195
import Tree from "rc-tree";
196
197
const VirtualScrollingWithInteractions = () => {
198
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
199
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
200
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
201
202
// Generate structured data
203
const treeData = useMemo(() => {
204
const departments = ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'];
205
const teams = ['Frontend', 'Backend', 'DevOps', 'QA', 'Design'];
206
const people = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Henry'];
207
208
return departments.map((dept, deptIndex) => ({
209
key: `dept-${deptIndex}`,
210
title: `${dept} Department`,
211
children: teams.map((team, teamIndex) => ({
212
key: `dept-${deptIndex}-team-${teamIndex}`,
213
title: `${team} Team`,
214
children: people.map((person, personIndex) => ({
215
key: `dept-${deptIndex}-team-${teamIndex}-person-${personIndex}`,
216
title: `${person} (${team})`,
217
isLeaf: true,
218
})),
219
})),
220
}));
221
}, []);
222
223
const totalNodes = useMemo(() => {
224
let count = 0;
225
const countNodes = (nodes: any[]): void => {
226
nodes.forEach(node => {
227
count++;
228
if (node.children) {
229
countNodes(node.children);
230
}
231
});
232
};
233
countNodes(treeData);
234
return count;
235
}, [treeData]);
236
237
return (
238
<div>
239
<div style={{ marginBottom: 16 }}>
240
<h3>Organization Tree (Virtual Scrolling)</h3>
241
<p>Total nodes: {totalNodes}</p>
242
<p>Selected: {selectedKeys.length} | Checked: {checkedKeys.length}</p>
243
</div>
244
245
<Tree
246
prefixCls="rc-tree"
247
virtual
248
height={600}
249
itemHeight={28}
250
checkable
251
selectable
252
multiple
253
treeData={treeData}
254
selectedKeys={selectedKeys}
255
checkedKeys={checkedKeys}
256
expandedKeys={expandedKeys}
257
onSelect={(keys, info) => {
258
console.log('Virtual tree selection:', keys.length, 'items');
259
setSelectedKeys(keys);
260
}}
261
onCheck={(checked, info) => {
262
console.log('Virtual tree check:', Array.isArray(checked) ? checked.length : checked.checked.length, 'items');
263
const keys = Array.isArray(checked) ? checked : checked.checked;
264
setCheckedKeys(keys);
265
}}
266
onExpand={(keys) => {
267
console.log('Virtual tree expand:', keys.length, 'expanded');
268
setExpandedKeys(keys);
269
}}
270
defaultExpandParent={false}
271
/>
272
</div>
273
);
274
};
275
```
276
277
### Programmatic Scrolling Control
278
279
```typescript
280
import React, { useState, useRef, useMemo } from "react";
281
import Tree from "rc-tree";
282
283
const ProgrammaticScrolling = () => {
284
const treeRef = useRef<any>(null);
285
const [searchIndex, setSearchIndex] = useState(0);
286
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
287
288
// Generate data with searchable items
289
const treeData = useMemo(() => {
290
return Array.from({ length: 200 }, (_, index) => ({
291
key: `item-${index}`,
292
title: `Searchable Item ${index}`,
293
isLeaf: true,
294
}));
295
}, []);
296
297
const scrollToItem = (index: number) => {
298
// Note: This is a conceptual example - actual scrollTo API may vary
299
if (treeRef.current && treeRef.current.scrollTo) {
300
treeRef.current.scrollTo(index);
301
}
302
};
303
304
const jumpToRandom = () => {
305
const randomIndex = Math.floor(Math.random() * treeData.length);
306
setSearchIndex(randomIndex);
307
scrollToItem(randomIndex);
308
};
309
310
const jumpToTop = () => {
311
scrollToItem(0);
312
setSearchIndex(0);
313
};
314
315
const jumpToBottom = () => {
316
const lastIndex = treeData.length - 1;
317
scrollToItem(lastIndex);
318
setSearchIndex(lastIndex);
319
};
320
321
return (
322
<div>
323
<div style={{ marginBottom: 16 }}>
324
<h3>Programmatic Scrolling Control</h3>
325
<div style={{ marginBottom: 8 }}>
326
<button onClick={jumpToTop} style={{ marginRight: 8 }}>
327
Jump to Top
328
</button>
329
<button onClick={jumpToRandom} style={{ marginRight: 8 }}>
330
Jump to Random
331
</button>
332
<button onClick={jumpToBottom} style={{ marginRight: 8 }}>
333
Jump to Bottom
334
</button>
335
</div>
336
<div>
337
<label>
338
Jump to index:
339
<input
340
type="number"
341
min="0"
342
max={treeData.length - 1}
343
value={searchIndex}
344
onChange={(e) => {
345
const index = parseInt(e.target.value);
346
setSearchIndex(index);
347
scrollToItem(index);
348
}}
349
style={{ marginLeft: 8, width: 80 }}
350
/>
351
</label>
352
</div>
353
</div>
354
355
<Tree
356
ref={treeRef}
357
prefixCls="rc-tree"
358
virtual
359
height={400}
360
itemHeight={30}
361
treeData={treeData}
362
expandedKeys={expandedKeys}
363
onExpand={setExpandedKeys}
364
/>
365
</div>
366
);
367
};
368
```
369
370
## Performance Considerations
371
372
### Virtual Scrolling Best Practices
373
374
```typescript { .api }
375
/**
376
* Performance optimization guidelines for virtual scrolling
377
*/
378
interface VirtualScrollingBestPractices {
379
/** Use consistent itemHeight for best performance */
380
itemHeight: number;
381
/** Set reasonable container height (avoid viewport height) */
382
height: number;
383
/** Minimize re-renders by memoizing data and callbacks */
384
memoization: boolean;
385
/** Use expandedKeys state management efficiently */
386
expandedKeysOptimization: boolean;
387
}
388
```
389
390
### Memory Optimization Example
391
392
```typescript
393
import React, { useState, useMemo, useCallback } from "react";
394
import Tree from "rc-tree";
395
396
const OptimizedVirtualTree = () => {
397
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
398
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
399
400
// Memoize large dataset generation
401
const treeData = useMemo(() => {
402
console.log('Generating tree data...');
403
return Array.from({ length: 10000 }, (_, index) => ({
404
key: `node-${index}`,
405
title: `Node ${index}`,
406
// Add some variety
407
disabled: index % 100 === 0,
408
checkable: index % 10 !== 0,
409
isLeaf: true,
410
}));
411
}, []);
412
413
// Memoize event handlers to prevent unnecessary re-renders
414
const handleExpand = useCallback((keys: string[]) => {
415
console.log('Expand changed:', keys.length);
416
setExpandedKeys(keys);
417
}, []);
418
419
const handleSelect = useCallback((keys: string[], info: any) => {
420
console.log('Selection changed:', keys.length);
421
setSelectedKeys(keys);
422
}, []);
423
424
// Memoize title renderer
425
const titleRender = useCallback((node: any) => {
426
return (
427
<span style={{
428
color: node.disabled ? '#ccc' : '#000',
429
fontWeight: selectedKeys.includes(node.key) ? 'bold' : 'normal',
430
}}>
431
{node.title}
432
{node.disabled && ' (disabled)'}
433
</span>
434
);
435
}, [selectedKeys]);
436
437
return (
438
<div>
439
<h3>Optimized Virtual Tree (10K items)</h3>
440
<p>Expanded: {expandedKeys.length} | Selected: {selectedKeys.length}</p>
441
442
<Tree
443
prefixCls="rc-tree"
444
virtual
445
height={500}
446
itemHeight={26}
447
selectable
448
multiple
449
treeData={treeData}
450
expandedKeys={expandedKeys}
451
selectedKeys={selectedKeys}
452
onExpand={handleExpand}
453
onSelect={handleSelect}
454
titleRender={titleRender}
455
/>
456
</div>
457
);
458
};
459
```
460
461
## Virtual Scrolling Limitations
462
463
### Known Limitations
464
465
```typescript { .api }
466
/**
467
* Virtual scrolling limitations to be aware of
468
*/
469
interface VirtualScrollingLimitations {
470
/** Fixed item height required for best performance */
471
fixedItemHeight: boolean;
472
/** Dynamic height items may cause scroll jumping */
473
dynamicHeightIssues: boolean;
474
/** Drag and drop may have limitations with virtual items */
475
dragDropConstraints: boolean;
476
/** Complex animations may not work well */
477
animationLimitations: boolean;
478
}
479
```
480
481
### Workarounds and Solutions
482
483
```typescript
484
import React, { useState, useMemo } from "react";
485
import Tree from "rc-tree";
486
487
const VirtualScrollingWorkarounds = () => {
488
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
489
490
// Solution: Normalize item heights by controlling content
491
const treeData = useMemo(() => {
492
return Array.from({ length: 1000 }, (_, index) => ({
493
key: `item-${index}`,
494
title: `Item ${index}`, // Keep titles consistent length
495
// Avoid dynamic content that changes height
496
description: index % 2 === 0 ? 'Even item' : 'Odd item',
497
isLeaf: true,
498
}));
499
}, []);
500
501
// Solution: Custom title renderer that maintains consistent height
502
const titleRender = (node: any) => (
503
<div style={{
504
height: 24, // Fixed height
505
lineHeight: '24px',
506
overflow: 'hidden',
507
textOverflow: 'ellipsis',
508
whiteSpace: 'nowrap',
509
}}>
510
<strong>{node.title}</strong>
511
<span style={{ color: '#666', marginLeft: 8 }}>
512
{node.description}
513
</span>
514
</div>
515
);
516
517
return (
518
<Tree
519
prefixCls="rc-tree"
520
virtual
521
height={400}
522
itemHeight={24} // Match the fixed height in titleRender
523
treeData={treeData}
524
titleRender={titleRender}
525
expandedKeys={expandedKeys}
526
onExpand={setExpandedKeys}
527
/>
528
);
529
};
530
```
531
532
## Integration with Other Features
533
534
### Virtual Scrolling + Async Loading
535
536
```typescript
537
import React, { useState, useCallback } from "react";
538
import Tree from "rc-tree";
539
540
const VirtualAsyncTree = () => {
541
const [treeData, setTreeData] = useState([
542
{ key: 'root', title: 'Root (click to load)', children: [] },
543
]);
544
const [loadedKeys, setLoadedKeys] = useState<string[]>([]);
545
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
546
547
const loadData = useCallback(async (treeNode: any) => {
548
const { key } = treeNode;
549
550
// Simulate loading large dataset
551
await new Promise(resolve => setTimeout(resolve, 1000));
552
553
// Generate many children for virtual scrolling
554
const children = Array.from({ length: 500 }, (_, index) => ({
555
key: `${key}-child-${index}`,
556
title: `Child ${index}`,
557
isLeaf: true,
558
}));
559
560
setTreeData(prevData => {
561
const updateNode = (nodes: any[]): any[] => {
562
return nodes.map(node => {
563
if (node.key === key) {
564
return { ...node, children };
565
}
566
if (node.children) {
567
return { ...node, children: updateNode(node.children) };
568
}
569
return node;
570
});
571
};
572
return updateNode(prevData);
573
});
574
575
setLoadedKeys(prev => [...prev, key]);
576
}, []);
577
578
return (
579
<Tree
580
prefixCls="rc-tree"
581
virtual
582
height={400}
583
itemHeight={24}
584
treeData={treeData}
585
loadData={loadData}
586
loadedKeys={loadedKeys}
587
expandedKeys={expandedKeys}
588
onExpand={setExpandedKeys}
589
/>
590
);
591
};
592
```