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

async-loading.mddocs/

0

# Async Loading

1

2

RC Tree supports asynchronous data loading for lazy-loading tree nodes with loading states, error handling, and integration with virtual scrolling for large datasets.

3

4

## Capabilities

5

6

### Async Loading Configuration

7

8

Configure asynchronous data loading with comprehensive state management and event handling.

9

10

```typescript { .api }

11

/**

12

* Asynchronous loading configuration

13

*/

14

interface AsyncLoadingConfig<TreeDataType extends BasicDataNode = DataNode> {

15

/** Function to load data for a tree node */

16

loadData?: (treeNode: EventDataNode<TreeDataType>) => Promise<any>;

17

/** Keys of nodes that have completed loading */

18

loadedKeys?: Key[];

19

}

20

21

/**

22

* Load data function signature

23

*/

24

type LoadDataFunction<TreeDataType extends BasicDataNode = DataNode> = (

25

treeNode: EventDataNode<TreeDataType>

26

) => Promise<any>;

27

28

/**

29

* Load completion event handler

30

*/

31

interface LoadEventHandler<TreeDataType extends BasicDataNode = DataNode> {

32

onLoad?: (

33

loadedKeys: Key[],

34

info: {

35

event: 'load';

36

node: EventDataNode<TreeDataType>;

37

},

38

) => void;

39

}

40

```

41

42

### Loading States

43

44

Tree nodes automatically manage loading states during async operations.

45

46

```typescript { .api }

47

/**

48

* Node loading state properties

49

*/

50

interface NodeLoadingState {

51

/** Whether the node is currently loading */

52

loading?: boolean;

53

/** Whether the node has completed loading */

54

loaded?: boolean;

55

}

56

57

/**

58

* Enhanced event data node with loading state

59

*/

60

interface LoadingEventDataNode<TreeDataType> extends EventDataNode<TreeDataType> {

61

/** Current loading state */

62

loading: boolean;

63

/** Whether loading has completed */

64

loaded: boolean;

65

}

66

```

67

68

**Usage Examples:**

69

70

### Basic Async Loading

71

72

```typescript

73

import React, { useState } from "react";

74

import Tree from "rc-tree";

75

76

interface AsyncNodeData {

77

key: string;

78

title: string;

79

isLeaf?: boolean;

80

children?: AsyncNodeData[];

81

}

82

83

const BasicAsyncLoading = () => {

84

const [treeData, setTreeData] = useState<AsyncNodeData[]>([

85

{

86

key: '0-0',

87

title: 'Expandable Node 1',

88

},

89

{

90

key: '0-1',

91

title: 'Expandable Node 2',

92

},

93

{

94

key: '0-2',

95

title: 'Leaf Node',

96

isLeaf: true,

97

},

98

]);

99

const [loadedKeys, setLoadedKeys] = useState<string[]>([]);

100

101

const loadData = async (treeNode: any): Promise<void> => {

102

const { key } = treeNode;

103

104

console.log('Loading data for node:', key);

105

106

// Simulate API call delay

107

await new Promise(resolve => setTimeout(resolve, 1500));

108

109

// Simulate potential loading failure

110

if (Math.random() < 0.1) {

111

throw new Error(`Failed to load data for ${key}`);

112

}

113

114

// Generate child nodes

115

const children: AsyncNodeData[] = [

116

{

117

key: `${key}-0`,

118

title: `Child 1 of ${key}`,

119

isLeaf: true,

120

},

121

{

122

key: `${key}-1`,

123

title: `Child 2 of ${key}`,

124

},

125

{

126

key: `${key}-2`,

127

title: `Child 3 of ${key}`,

128

isLeaf: true,

129

},

130

];

131

132

// Update tree data

133

setTreeData(prevData => {

134

const updateNode = (nodes: AsyncNodeData[]): AsyncNodeData[] => {

135

return nodes.map(node => {

136

if (node.key === key) {

137

return { ...node, children };

138

}

139

if (node.children) {

140

return { ...node, children: updateNode(node.children) };

141

}

142

return node;

143

});

144

};

145

return updateNode(prevData);

146

});

147

148

// Mark as loaded

149

setLoadedKeys(prev => [...prev, key]);

150

};

151

152

return (

153

<Tree

154

prefixCls="rc-tree"

155

treeData={treeData}

156

loadData={loadData}

157

loadedKeys={loadedKeys}

158

onLoad={(keys, info) => {

159

console.log('Load completed:', keys, info);

160

}}

161

/>

162

);

163

};

164

```

165

166

### Async Loading with Error Handling

167

168

```typescript

169

import React, { useState } from "react";

170

import Tree from "rc-tree";

171

172

interface ErrorHandlingNodeData {

173

key: string;

174

title: string;

175

isLeaf?: boolean;

176

hasError?: boolean;

177

errorMessage?: string;

178

children?: ErrorHandlingNodeData[];

179

}

180

181

const AsyncLoadingWithErrorHandling = () => {

182

const [treeData, setTreeData] = useState<ErrorHandlingNodeData[]>([

183

{ key: '0-0', title: 'Reliable Node' },

184

{ key: '0-1', title: 'Unreliable Node (50% fail rate)' },

185

{ key: '0-2', title: 'Always Fails Node' },

186

]);

187

const [loadedKeys, setLoadedKeys] = useState<string[]>([]);

188

const [expandedKeys, setExpandedKeys] = useState<string[]>([]);

189

190

const loadData = async (treeNode: any): Promise<void> => {

191

const { key } = treeNode;

192

193

try {

194

// Simulate different failure rates

195

let failureRate = 0;

196

if (key.includes('0-1')) failureRate = 0.5;

197

if (key.includes('0-2')) failureRate = 1.0;

198

199

if (Math.random() < failureRate) {

200

throw new Error(`Network error loading ${key}`);

201

}

202

203

// Simulate loading delay

204

await new Promise(resolve => setTimeout(resolve, 1000));

205

206

// Generate children

207

const children: ErrorHandlingNodeData[] = [

208

{ key: `${key}-0`, title: `Child 1 of ${key}`, isLeaf: true },

209

{ key: `${key}-1`, title: `Child 2 of ${key}`, isLeaf: true },

210

];

211

212

// Update tree data with successful load

213

setTreeData(prevData => {

214

const updateNode = (nodes: ErrorHandlingNodeData[]): ErrorHandlingNodeData[] => {

215

return nodes.map(node => {

216

if (node.key === key) {

217

return {

218

...node,

219

children,

220

hasError: false,

221

errorMessage: undefined,

222

};

223

}

224

if (node.children) {

225

return { ...node, children: updateNode(node.children) };

226

}

227

return node;

228

});

229

};

230

return updateNode(prevData);

231

});

232

233

setLoadedKeys(prev => [...prev, key]);

234

235

} catch (error) {

236

console.error('Load failed:', error);

237

238

// Update tree data with error state

239

setTreeData(prevData => {

240

const updateNode = (nodes: ErrorHandlingNodeData[]): ErrorHandlingNodeData[] => {

241

return nodes.map(node => {

242

if (node.key === key) {

243

return {

244

...node,

245

hasError: true,

246

errorMessage: error instanceof Error ? error.message : 'Unknown error',

247

children: [],

248

};

249

}

250

if (node.children) {

251

return { ...node, children: updateNode(node.children) };

252

}

253

return node;

254

});

255

};

256

return updateNode(prevData);

257

});

258

259

// Still mark as "loaded" to prevent infinite retry

260

setLoadedKeys(prev => [...prev, key]);

261

}

262

};

263

264

const titleRender = (node: ErrorHandlingNodeData) => (

265

<span>

266

{node.title}

267

{node.hasError && (

268

<span style={{ color: 'red', marginLeft: 8 }}>

269

❌ ({node.errorMessage})

270

</span>

271

)}

272

</span>

273

);

274

275

return (

276

<Tree

277

prefixCls="rc-tree"

278

treeData={treeData}

279

loadData={loadData}

280

loadedKeys={loadedKeys}

281

expandedKeys={expandedKeys}

282

onExpand={setExpandedKeys}

283

titleRender={titleRender}

284

/>

285

);

286

};

287

```

288

289

### Async Loading with Caching

290

291

```typescript

292

import React, { useState, useCallback, useRef } from "react";

293

import Tree from "rc-tree";

294

295

interface CachedNodeData {

296

key: string;

297

title: string;

298

type: 'folder' | 'file';

299

isLeaf?: boolean;

300

children?: CachedNodeData[];

301

}

302

303

const AsyncLoadingWithCaching = () => {

304

const [treeData, setTreeData] = useState<CachedNodeData[]>([

305

{ key: 'root', title: 'Root Directory', type: 'folder' },

306

]);

307

const [loadedKeys, setLoadedKeys] = useState<string[]>([]);

308

const [expandedKeys, setExpandedKeys] = useState<string[]>([]);

309

310

// Cache to store loaded data

311

const cacheRef = useRef<Map<string, CachedNodeData[]>>(new Map());

312

313

const loadData = useCallback(async (treeNode: any): Promise<void> => {

314

const { key } = treeNode;

315

316

// Check cache first

317

const cachedData = cacheRef.current.get(key);

318

if (cachedData) {

319

console.log('Using cached data for:', key);

320

321

// Update tree with cached data

322

setTreeData(prevData => {

323

const updateNode = (nodes: CachedNodeData[]): CachedNodeData[] => {

324

return nodes.map(node => {

325

if (node.key === key) {

326

return { ...node, children: cachedData };

327

}

328

if (node.children) {

329

return { ...node, children: updateNode(node.children) };

330

}

331

return node;

332

});

333

};

334

return updateNode(prevData);

335

});

336

337

setLoadedKeys(prev => [...prev, key]);

338

return;

339

}

340

341

console.log('Loading fresh data for:', key);

342

343

// Simulate API call

344

await new Promise(resolve => setTimeout(resolve, 1200));

345

346

// Generate directory contents

347

const children: CachedNodeData[] = [

348

{ key: `${key}/documents`, title: 'Documents', type: 'folder' },

349

{ key: `${key}/images`, title: 'Images', type: 'folder' },

350

{ key: `${key}/readme.txt`, title: 'readme.txt', type: 'file', isLeaf: true },

351

{ key: `${key}/config.json`, title: 'config.json', type: 'file', isLeaf: true },

352

];

353

354

// Cache the loaded data

355

cacheRef.current.set(key, children);

356

357

// Update tree data

358

setTreeData(prevData => {

359

const updateNode = (nodes: CachedNodeData[]): CachedNodeData[] => {

360

return nodes.map(node => {

361

if (node.key === key) {

362

return { ...node, children };

363

}

364

if (node.children) {

365

return { ...node, children: updateNode(node.children) };

366

}

367

return node;

368

});

369

};

370

return updateNode(prevData);

371

});

372

373

setLoadedKeys(prev => [...prev, key]);

374

}, []);

375

376

const clearCache = () => {

377

cacheRef.current.clear();

378

setLoadedKeys([]);

379

setExpandedKeys([]);

380

setTreeData([{ key: 'root', title: 'Root Directory', type: 'folder' }]);

381

};

382

383

const titleRender = (node: CachedNodeData) => (

384

<span>

385

{node.type === 'folder' ? 'πŸ“' : 'πŸ“„'} {node.title}

386

{cacheRef.current.has(node.key) && (

387

<span style={{ color: 'green', marginLeft: 8 }}>πŸ“¦</span>

388

)}

389

</span>

390

);

391

392

return (

393

<div>

394

<div style={{ marginBottom: 16 }}>

395

<button onClick={clearCache}>Clear Cache & Reset</button>

396

<p>Cache size: {cacheRef.current.size} items</p>

397

</div>

398

399

<Tree

400

prefixCls="rc-tree"

401

treeData={treeData}

402

loadData={loadData}

403

loadedKeys={loadedKeys}

404

expandedKeys={expandedKeys}

405

onExpand={setExpandedKeys}

406

titleRender={titleRender}

407

/>

408

</div>

409

);

410

};

411

```

412

413

### Async Loading with Search

414

415

```typescript

416

import React, { useState, useCallback, useMemo } from "react";

417

import Tree from "rc-tree";

418

419

interface SearchableNodeData {

420

key: string;

421

title: string;

422

description: string;

423

category: string;

424

isLeaf?: boolean;

425

children?: SearchableNodeData[];

426

}

427

428

const AsyncLoadingWithSearch = () => {

429

const [treeData, setTreeData] = useState<SearchableNodeData[]>([

430

{

431

key: 'products',

432

title: 'Products',

433

description: 'Product catalog',

434

category: 'root',

435

},

436

{

437

key: 'services',

438

title: 'Services',

439

description: 'Service offerings',

440

category: 'root',

441

},

442

]);

443

const [loadedKeys, setLoadedKeys] = useState<string[]>([]);

444

const [expandedKeys, setExpandedKeys] = useState<string[]>([]);

445

const [searchTerm, setSearchTerm] = useState('');

446

447

const loadData = useCallback(async (treeNode: any): Promise<void> => {

448

const { key } = treeNode;

449

450

// Simulate API search/load

451

await new Promise(resolve => setTimeout(resolve, 800));

452

453

let children: SearchableNodeData[] = [];

454

455

if (key === 'products') {

456

children = [

457

{ key: 'electronics', title: 'Electronics', description: 'Electronic devices and gadgets', category: 'product' },

458

{ key: 'books', title: 'Books', description: 'Physical and digital books', category: 'product' },

459

{ key: 'clothing', title: 'Clothing', description: 'Apparel and accessories', category: 'product' },

460

];

461

} else if (key === 'services') {

462

children = [

463

{ key: 'consulting', title: 'Consulting', description: 'Professional consulting services', category: 'service', isLeaf: true },

464

{ key: 'support', title: 'Support', description: 'Technical support services', category: 'service', isLeaf: true },

465

{ key: 'training', title: 'Training', description: 'Educational and training programs', category: 'service', isLeaf: true },

466

];

467

} else {

468

// Second level loading

469

children = Array.from({ length: 5 }, (_, index) => ({

470

key: `${key}-item-${index}`,

471

title: `${key} Item ${index + 1}`,

472

description: `Detailed description for ${key} item ${index + 1}`,

473

category: 'item',

474

isLeaf: true,

475

}));

476

}

477

478

// Filter children based on search term

479

if (searchTerm) {

480

children = children.filter(child =>

481

child.title.toLowerCase().includes(searchTerm.toLowerCase()) ||

482

child.description.toLowerCase().includes(searchTerm.toLowerCase())

483

);

484

}

485

486

setTreeData(prevData => {

487

const updateNode = (nodes: SearchableNodeData[]): SearchableNodeData[] => {

488

return nodes.map(node => {

489

if (node.key === key) {

490

return { ...node, children };

491

}

492

if (node.children) {

493

return { ...node, children: updateNode(node.children) };

494

}

495

return node;

496

});

497

};

498

return updateNode(prevData);

499

});

500

501

setLoadedKeys(prev => [...prev, key]);

502

}, [searchTerm]);

503

504

// Re-load data when search term changes

505

const handleSearchChange = (value: string) => {

506

setSearchTerm(value);

507

// Clear loaded keys to force re-loading with new search criteria

508

setLoadedKeys([]);

509

setExpandedKeys([]);

510

};

511

512

const titleRender = (node: SearchableNodeData) => {

513

const highlightText = (text: string, highlight: string) => {

514

if (!highlight) return text;

515

516

const parts = text.split(new RegExp(`(${highlight})`, 'gi'));

517

return parts.map((part, index) =>

518

part.toLowerCase() === highlight.toLowerCase()

519

? <mark key={index}>{part}</mark>

520

: part

521

);

522

};

523

524

return (

525

<div>

526

<strong>{highlightText(node.title, searchTerm)}</strong>

527

<div style={{ fontSize: '0.8em', color: '#666' }}>

528

{highlightText(node.description, searchTerm)}

529

</div>

530

</div>

531

);

532

};

533

534

return (

535

<div>

536

<div style={{ marginBottom: 16 }}>

537

<input

538

type="text"

539

placeholder="Search items..."

540

value={searchTerm}

541

onChange={(e) => handleSearchChange(e.target.value)}

542

style={{ width: '100%', padding: 8 }}

543

/>

544

</div>

545

546

<Tree

547

prefixCls="rc-tree"

548

treeData={treeData}

549

loadData={loadData}

550

loadedKeys={loadedKeys}

551

expandedKeys={expandedKeys}

552

onExpand={setExpandedKeys}

553

titleRender={titleRender}

554

itemHeight={48} // Taller for two-line content

555

/>

556

</div>

557

);

558

};

559

```

560

561

## Advanced Async Loading Patterns

562

563

### Lazy Loading with Pagination

564

565

```typescript

566

import React, { useState, useCallback } from "react";

567

import Tree from "rc-tree";

568

569

interface PaginatedNodeData {

570

key: string;

571

title: string;

572

hasMore?: boolean;

573

page?: number;

574

isLeaf?: boolean;

575

children?: PaginatedNodeData[];

576

}

577

578

const PaginatedAsyncLoading = () => {

579

const [treeData, setTreeData] = useState<PaginatedNodeData[]>([

580

{ key: 'dataset-1', title: 'Large Dataset 1 (1000+ items)', hasMore: true, page: 0 },

581

{ key: 'dataset-2', title: 'Large Dataset 2 (500+ items)', hasMore: true, page: 0 },

582

]);

583

const [loadedKeys, setLoadedKeys] = useState<string[]>([]);

584

const [expandedKeys, setExpandedKeys] = useState<string[]>([]);

585

586

const loadData = useCallback(async (treeNode: any): Promise<void> => {

587

const { key } = treeNode;

588

589

// Find the node to get current page

590

const findNode = (nodes: PaginatedNodeData[], targetKey: string): PaginatedNodeData | null => {

591

for (const node of nodes) {

592

if (node.key === targetKey) return node;

593

if (node.children) {

594

const found = findNode(node.children, targetKey);

595

if (found) return found;

596

}

597

}

598

return null;

599

};

600

601

const nodeData = findNode(treeData, key);

602

const currentPage = nodeData?.page || 0;

603

const pageSize = 20;

604

605

console.log(`Loading page ${currentPage + 1} for ${key}`);

606

607

// Simulate paginated API call

608

await new Promise(resolve => setTimeout(resolve, 1000));

609

610

// Generate page of data

611

const startIndex = currentPage * pageSize;

612

const pageItems: PaginatedNodeData[] = Array.from({ length: pageSize }, (_, index) => ({

613

key: `${key}-item-${startIndex + index}`,

614

title: `Item ${startIndex + index + 1}`,

615

isLeaf: true,

616

}));

617

618

// Add "Load More" item if there are more pages

619

const totalItems = key === 'dataset-1' ? 1000 : 500;

620

const hasMore = (startIndex + pageSize) < totalItems;

621

622

if (hasMore) {

623

pageItems.push({

624

key: `${key}-load-more-${currentPage + 1}`,

625

title: `πŸ“„ Load More... (${totalItems - startIndex - pageSize} remaining)`,

626

hasMore: true,

627

page: currentPage + 1,

628

});

629

}

630

631

setTreeData(prevData => {

632

const updateNode = (nodes: PaginatedNodeData[]): PaginatedNodeData[] => {

633

return nodes.map(node => {

634

if (node.key === key) {

635

const existingChildren = node.children || [];

636

return {

637

...node,

638

children: [...existingChildren, ...pageItems],

639

hasMore: false, // This node itself no longer needs loading

640

};

641

}

642

if (node.children) {

643

return { ...node, children: updateNode(node.children) };

644

}

645

return node;

646

});

647

};

648

return updateNode(prevData);

649

});

650

651

setLoadedKeys(prev => [...prev, key]);

652

}, [treeData]);

653

654

return (

655

<Tree

656

prefixCls="rc-tree"

657

treeData={treeData}

658

loadData={loadData}

659

loadedKeys={loadedKeys}

660

expandedKeys={expandedKeys}

661

onExpand={setExpandedKeys}

662

/>

663

);

664

};

665

```

666

667

### Real-time Data Loading

668

669

```typescript

670

import React, { useState, useCallback, useEffect, useRef } from "react";

671

import Tree from "rc-tree";

672

673

interface RealtimeNodeData {

674

key: string;

675

title: string;

676

lastUpdated?: Date;

677

isLive?: boolean;

678

children?: RealtimeNodeData[];

679

}

680

681

const RealtimeAsyncLoading = () => {

682

const [treeData, setTreeData] = useState<RealtimeNodeData[]>([

683

{ key: 'live-feeds', title: 'πŸ“‘ Live Data Feeds', isLive: true },

684

{ key: 'static-data', title: 'πŸ“ Static Data' },

685

]);

686

const [loadedKeys, setLoadedKeys] = useState<string[]>([]);

687

const [expandedKeys, setExpandedKeys] = useState<string[]>([]);

688

const intervalsRef = useRef<Map<string, NodeJS.Timeout>>(new Map());

689

690

const loadData = useCallback(async (treeNode: any): Promise<void> => {

691

const { key } = treeNode;

692

693

await new Promise(resolve => setTimeout(resolve, 500));

694

695

let children: RealtimeNodeData[] = [];

696

697

if (key === 'live-feeds') {

698

children = [

699

{ key: 'sensor-1', title: 'Temperature Sensor', isLive: true, lastUpdated: new Date() },

700

{ key: 'sensor-2', title: 'Humidity Sensor', isLive: true, lastUpdated: new Date() },

701

{ key: 'sensor-3', title: 'Pressure Sensor', isLive: true, lastUpdated: new Date() },

702

];

703

704

// Start real-time updates for live nodes

705

children.forEach(child => {

706

if (child.isLive && !intervalsRef.current.has(child.key)) {

707

const interval = setInterval(() => {

708

setTreeData(prevData => {

709

const updateNode = (nodes: RealtimeNodeData[]): RealtimeNodeData[] => {

710

return nodes.map(node => {

711

if (node.key === child.key) {

712

return {

713

...node,

714

lastUpdated: new Date(),

715

title: `${child.title.split(' (')[0]} (${Math.random().toFixed(2)})`,

716

};

717

}

718

if (node.children) {

719

return { ...node, children: updateNode(node.children) };

720

}

721

return node;

722

});

723

};

724

return updateNode(prevData);

725

});

726

}, 2000);

727

728

intervalsRef.current.set(child.key, interval);

729

}

730

});

731

} else {

732

children = [

733

{ key: 'static-1', title: 'Static File 1', lastUpdated: new Date('2024-01-01') },

734

{ key: 'static-2', title: 'Static File 2', lastUpdated: new Date('2024-01-02') },

735

];

736

}

737

738

setTreeData(prevData => {

739

const updateNode = (nodes: RealtimeNodeData[]): RealtimeNodeData[] => {

740

return nodes.map(node => {

741

if (node.key === key) {

742

return { ...node, children };

743

}

744

if (node.children) {

745

return { ...node, children: updateNode(node.children) };

746

}

747

return node;

748

});

749

};

750

return updateNode(prevData);

751

});

752

753

setLoadedKeys(prev => [...prev, key]);

754

}, []);

755

756

// Cleanup intervals on unmount

757

useEffect(() => {

758

return () => {

759

intervalsRef.current.forEach(interval => clearInterval(interval));

760

intervalsRef.current.clear();

761

};

762

}, []);

763

764

const titleRender = (node: RealtimeNodeData) => (

765

<span>

766

{node.title}

767

{node.isLive && <span style={{ color: 'green' }}> πŸ”΄</span>}

768

{node.lastUpdated && (

769

<span style={{ fontSize: '0.8em', color: '#666', marginLeft: 8 }}>

770

{node.lastUpdated.toLocaleTimeString()}

771

</span>

772

)}

773

</span>

774

);

775

776

return (

777

<div>

778

<h3>Real-time Data Tree</h3>

779

<p>Live sensors update every 2 seconds</p>

780

781

<Tree

782

prefixCls="rc-tree"

783

treeData={treeData}

784

loadData={loadData}

785

loadedKeys={loadedKeys}

786

expandedKeys={expandedKeys}

787

onExpand={setExpandedKeys}

788

titleRender={titleRender}

789

/>

790

</div>

791

);

792

};

793

```

794

795

## Best Practices

796

797

### Error Boundaries for Async Loading

798

799

```typescript

800

import React, { Component, ReactNode } from "react";

801

802

interface ErrorBoundaryState {

803

hasError: boolean;

804

error?: Error;

805

}

806

807

class AsyncTreeErrorBoundary extends Component<

808

{ children: ReactNode },

809

ErrorBoundaryState

810

> {

811

constructor(props: { children: ReactNode }) {

812

super(props);

813

this.state = { hasError: false };

814

}

815

816

static getDerivedStateFromError(error: Error): ErrorBoundaryState {

817

return { hasError: true, error };

818

}

819

820

componentDidCatch(error: Error, errorInfo: any) {

821

console.error('Tree async loading error:', error, errorInfo);

822

}

823

824

render() {

825

if (this.state.hasError) {

826

return (

827

<div style={{ padding: 16, border: '1px solid #ff4d4f', borderRadius: 4 }}>

828

<h4>Tree Loading Error</h4>

829

<p>Something went wrong while loading tree data.</p>

830

<button onClick={() => this.setState({ hasError: false })}>

831

Try Again

832

</button>

833

</div>

834

);

835

}

836

837

return this.props.children;

838

}

839

}

840

841

// Usage example

842

const SafeAsyncTree = () => (

843

<AsyncTreeErrorBoundary>

844

<AsyncLoadingTree />

845

</AsyncTreeErrorBoundary>

846

);

847

```