CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-rc-tree

Tree UI component for React with selection, checkboxes, drag-drop, and virtual scrolling features

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

data-management.mddocs/

Data Management

RC Tree supports flexible data management through both JSX children and data-driven approaches, with customizable field mappings and comprehensive type-safe data structures.

Capabilities

Data Structure Types

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;

Field Name Mapping

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:

Basic Data-Driven Tree

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
    />
  );
};

Custom Field Mapping

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
    />
  );
};

Rich Data with Custom Properties

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
    />
  );
};

Dynamic Data with Async Loading

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}
    />
  );
};

Data Structure Patterns

Flat Data Conversion

/**
 * 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
    />
  );
};

Type-Safe Custom Data

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 Management

Key Requirements

/**
 * 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

docs

async-loading.md

data-management.md

drag-drop.md

index.md

selection-checking.md

tree-component.md

tree-node.md

virtual-scrolling.md

tile.json