Tree UI component for React with selection, checkboxes, drag-drop, and virtual scrolling features
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
RC Tree supports flexible data management through both JSX children and data-driven approaches, with customizable field mappings and comprehensive type-safe data structures.
Core data types for representing tree nodes with comprehensive type safety and customization options.
/**
* Base interface for tree data nodes with common properties
*/
interface BasicDataNode {
/** Enable/disable checkbox for this node */
checkable?: boolean;
/** Disable all interactions for this node */
disabled?: boolean;
/** Disable only checkbox interactions */
disableCheckbox?: boolean;
/** Custom icon for this node */
icon?: IconType;
/** Mark as leaf node (no children) */
isLeaf?: boolean;
/** Enable/disable selection for this node */
selectable?: boolean;
/** Custom expand/collapse icon */
switcherIcon?: IconType;
/** CSS class for styling */
className?: string;
/** Inline styles */
style?: React.CSSProperties;
}
/**
* Generic wrapper type for custom data with tree functionality
* @param T - Custom data type
* @param ChildFieldName - Field name for children array (default: 'children')
*/
type FieldDataNode<T, ChildFieldName extends string = 'children'> = BasicDataNode &
T &
Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
/**
* Standard data node structure with key and title
*/
type DataNode = FieldDataNode<{
key: Key;
title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
}>;
/**
* Enhanced data node with event state information
*/
type EventDataNode<TreeDataType> = {
key: Key;
expanded: boolean;
selected: boolean;
checked: boolean;
loaded: boolean;
loading: boolean;
halfChecked: boolean;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
pos: string;
active: boolean;
} & TreeDataType & BasicDataNode;Customize field names for data-driven trees to work with existing data structures.
/**
* Configuration for mapping custom field names to tree properties
*/
interface FieldNames {
/** Field name for node display title (default: 'title') */
title?: string;
/** Internal title field mapping for complex scenarios */
_title?: string[];
/** Field name for unique node identifier (default: 'key') */
key?: string;
/** Field name for child nodes array (default: 'children') */
children?: string;
}Usage Examples:
import React, { useState } from "react";
import Tree from "rc-tree";
const BasicDataTree = () => {
const treeData = [
{
key: '0-0',
title: 'Parent Node',
children: [
{
key: '0-0-0',
title: 'Child Node 1',
isLeaf: true,
},
{
key: '0-0-1',
title: 'Child Node 2',
children: [
{
key: '0-0-1-0',
title: 'Grandchild Node',
isLeaf: true,
},
],
},
],
},
];
return (
<Tree
prefixCls="rc-tree"
treeData={treeData}
defaultExpandAll
/>
);
};import React from "react";
import Tree from "rc-tree";
const CustomFieldTree = () => {
// Data with custom field names
const customData = [
{
id: '1',
name: 'Root Category',
subcategories: [
{
id: '1-1',
name: 'Electronics',
subcategories: [
{ id: '1-1-1', name: 'Phones' },
{ id: '1-1-2', name: 'Laptops' },
],
},
{
id: '1-2',
name: 'Books',
subcategories: [],
},
],
},
];
const fieldNames = {
title: 'name',
key: 'id',
children: 'subcategories',
};
return (
<Tree
prefixCls="rc-tree"
treeData={customData}
fieldNames={fieldNames}
defaultExpandAll
/>
);
};import React from "react";
import Tree from "rc-tree";
interface CustomNodeData {
key: string;
title: string;
type: 'folder' | 'file';
size?: number;
modified?: Date;
children?: CustomNodeData[];
}
const RichDataTree = () => {
const richTreeData: CustomNodeData[] = [
{
key: '0-0',
title: 'Documents',
type: 'folder',
modified: new Date('2024-01-15'),
children: [
{
key: '0-0-0',
title: 'report.pdf',
type: 'file',
size: 2048,
modified: new Date('2024-01-10'),
isLeaf: true,
},
{
key: '0-0-1',
title: 'presentation.pptx',
type: 'file',
size: 5120,
modified: new Date('2024-01-12'),
isLeaf: true,
disabled: true, // File is read-only
},
],
},
];
const titleRender = (nodeData: CustomNodeData) => (
<span>
{nodeData.type === 'folder' ? '📁' : '📄'} {nodeData.title}
{nodeData.size && (
<span style={{ color: '#999', marginLeft: 8 }}>
({Math.round(nodeData.size / 1024)}KB)
</span>
)}
</span>
);
return (
<Tree
prefixCls="rc-tree"
treeData={richTreeData}
titleRender={titleRender}
defaultExpandAll
/>
);
};import React, { useState } from "react";
import Tree from "rc-tree";
interface AsyncNodeData {
key: string;
title: string;
isLeaf?: boolean;
children?: AsyncNodeData[];
}
const AsyncDataTree = () => {
const [treeData, setTreeData] = useState<AsyncNodeData[]>([
{
key: '0-0',
title: 'Lazy Parent',
children: [], // Empty children for lazy loading
},
]);
const [loadedKeys, setLoadedKeys] = useState<string[]>([]);
const loadData = async (treeNode: any) => {
const { key } = treeNode;
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
// Generate children data
const children = [
{
key: `${key}-0`,
title: `Child ${key}-0`,
isLeaf: true,
},
{
key: `${key}-1`,
title: `Child ${key}-1`,
isLeaf: true,
},
];
// Update tree data
setTreeData(prevData => {
const updateNode = (nodes: AsyncNodeData[]): AsyncNodeData[] => {
return nodes.map(node => {
if (node.key === key) {
return { ...node, children };
}
if (node.children) {
return { ...node, children: updateNode(node.children) };
}
return node;
});
};
return updateNode(prevData);
});
setLoadedKeys(prev => [...prev, key]);
};
return (
<Tree
prefixCls="rc-tree"
treeData={treeData}
loadData={loadData}
loadedKeys={loadedKeys}
/>
);
};/**
* Convert flat array data to hierarchical tree structure
*/
interface FlatDataItem {
id: string;
parentId?: string;
title: string;
[key: string]: any;
}
/**
* Utility type for flattened tree node with hierarchy information
*/
interface FlattenNode<TreeDataType extends BasicDataNode = DataNode> {
parent: FlattenNode<TreeDataType> | null;
children: FlattenNode<TreeDataType>[];
pos: string;
data: TreeDataType;
title: React.ReactNode;
key: Key;
isStart: boolean[];
isEnd: boolean[];
}Usage Example:
import React from "react";
import Tree from "rc-tree";
const FlatDataTree = () => {
const flatData = [
{ id: '1', title: 'Root' },
{ id: '2', parentId: '1', title: 'Child 1' },
{ id: '3', parentId: '1', title: 'Child 2' },
{ id: '4', parentId: '2', title: 'Grandchild 1' },
];
// Convert flat data to tree structure
const buildTreeData = (flatData: any[]) => {
const map = new Map();
const roots: any[] = [];
// Create map of all items
flatData.forEach(item => {
map.set(item.id, { ...item, key: item.id, children: [] });
});
// Build tree structure
flatData.forEach(item => {
const node = map.get(item.id);
if (item.parentId) {
const parent = map.get(item.parentId);
if (parent) {
parent.children.push(node);
}
} else {
roots.push(node);
}
});
return roots;
};
const treeData = buildTreeData(flatData);
return (
<Tree
prefixCls="rc-tree"
treeData={treeData}
defaultExpandAll
/>
);
};import React from "react";
import Tree from "rc-tree";
// Define your custom data structure
interface FileSystemNode {
key: string;
title: string;
type: 'file' | 'directory';
permissions: string;
owner: string;
size?: number;
children?: FileSystemNode[];
}
const TypeSafeDataTree = () => {
const fileSystemData: FileSystemNode[] = [
{
key: '/home',
title: 'home',
type: 'directory',
permissions: 'drwxr-xr-x',
owner: 'root',
children: [
{
key: '/home/user',
title: 'user',
type: 'directory',
permissions: 'drwxr-xr-x',
owner: 'user',
children: [
{
key: '/home/user/document.txt',
title: 'document.txt',
type: 'file',
permissions: '-rw-r--r--',
owner: 'user',
size: 1024,
isLeaf: true,
},
],
},
],
},
];
return (
<Tree<FileSystemNode>
prefixCls="rc-tree"
treeData={fileSystemData}
titleRender={(node) => (
<span>
{node.type === 'directory' ? '📁' : '📄'} {node.title}
<span style={{ color: '#666', fontSize: '0.8em', marginLeft: 8 }}>
{node.permissions} {node.owner}
</span>
</span>
)}
defaultExpandAll
/>
);
};/**
* Key types for tree node identification
*/
type Key = React.Key; // string | number
type SafeKey = Exclude<Key, bigint>; // Excludes bigint for index compatibility
/**
* Key entity mapping for efficient tree operations
*/
type KeyEntities<DateType extends BasicDataNode = any> = Record<
SafeKey,
DataEntity<DateType>
>;
/**
* Data entity with hierarchy information
*/
interface DataEntity<TreeDataType extends BasicDataNode = DataNode> {
node: TreeDataType;
nodes: TreeDataType[];
key: Key;
pos: string;
index: number;
parent?: DataEntity<TreeDataType>;
children?: DataEntity<TreeDataType>[];
level: number;
}Key Management Best Practices:
// ✅ Good: Unique, stable keys
const goodTreeData = [
{ key: 'user-1', title: 'John Doe' },
{ key: 'user-2', title: 'Jane Smith' },
];
// ❌ Bad: Non-unique or unstable keys
const badTreeData = [
{ key: 1, title: 'First' }, // Numbers can conflict
{ key: 1, title: 'Second' }, // Duplicate key
];
// ✅ Good: Hierarchical keys for complex structures
const hierarchicalData = [
{
key: 'department-engineering',
title: 'Engineering',
children: [
{ key: 'team-frontend', title: 'Frontend Team' },
{ key: 'team-backend', title: 'Backend Team' },
],
},
];Install with Tessl CLI
npx tessl i tessl/npm-rc-tree