or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-loading.mddata-management.mddrag-drop.mdindex.mdselection-checking.mdtree-component.mdtree-node.mdvirtual-scrolling.md

virtual-scrolling.mddocs/

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

```