# rc-component/tree keeps a tree view controllable down to the expansion keys

> The react-component/tree package is the tree view primitive behind the Ant Design ecosystem, offering controlled and uncontrolled expansion, selection, checking, drag and drop and async loading, with virtual scrolling for large trees. The API is a long table of props, and the split between controlled and default state is the part that determines how you use it.

**react-component/tree** — 🌳 Accessible tree view primitives for React, maintained in the Ant Design ecosystem.

- Repository: https://github.com/react-component/tree
- Website: https://react-component.github.io/tree
- Stars: 1,274 · Forks: 491
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/react-component-tree

## Controlled and uncontrolled for every piece of state

The highlights list is precise about what the component manages: controlled and uncontrolled expansion, selection, checking, drag and drop, and async loading. That means each of those five has two ways to be driven, and the prop table shows the pairing. For expansion there is `expandedKeys` for the controlled case and `defaultExpandedKeys` and `defaultExpandAll` and `defaultExpandParent` for the uncontrolled one. For selection there is `selectedKeys` and `defaultSelectedKeys`. For checking there is `checkedKeys` and `defaultCheckedKeys`. The rule the documentation states for the controlled props is the one worth internalising: after setting a controlled value, the matching default prop will not work. So you pick one mode per behaviour, and mixing them silently does nothing rather than erroring.

## The tree data shape is four fields

The data model is deliberately small. A `treeData` entry is an array of objects with a key, a title, optional children, and optional disabled and selectable flags, and the documentation notes that the key value should be unique across the whole array. A node can be a string or a React node for the title, and children are the same shape one level down. The usage example is short enough to be the whole tutorial:

```tsx
import Tree from '@rc-component/tree';
import type { DataNode } from '@rc-component/tree';
import '@rc-component/tree/assets/index.css';

const treeData: DataNode[] = [
  {
    key: '0',
    title: 'Documents',
    children: [{ key: '0-0', title: 'Roadmap' }],
  },
];

export default () => (
  <Tree defaultExpandAll treeData={treeData} onSelect={keys => console.log(keys)} />
);
```

The TypeScript definitions are part of the package, covering the tree data, the node props and the event payloads, so the shape of `onCheck` and `onSelect` is known at compile time rather than inferred from a README table.

## Checking has two modes and one gotcha

The `checkable` prop enables checkboxes, and the documentation spells out the association behaviour: parent and children nodes are associated, so if the parent node's key exists then all the children nodes will be checked, and vice versa. If you set `checkable` together with `checkStrictly`, the value should be an object containing a checked array and a halfChecked array rather than a plain array of keys, because strict checking is exactly the case where you need to distinguish the two states that the association normally collapses. The `checkedKeys` prop carries the same shape. This is the one place in the API where the type changes based on another prop, and it is the one most likely to catch you out if you copy an example that assumes the simpler behaviour.

## Drag and drop comes with a drop policy

Drag and drop is supported and configurable, and the props table is where the detail is. The `draggable` prop takes a boolean or a function of the node, and `allowDrop` takes a function receiving the drag node, the drop node and the drop position, so you decide what may be dropped where. There is a `dropIndicatorRender` prop for the visual indicator. The event handlers are a full set, `onDragStart`, `onDragEnter`, `onDragOver`, `onDragLeave`, `onDragEnd` and `onDrop`, each receiving the event and the node, and `onDrop` additionally receiving the drag node and the dragged node keys. The `direction` prop, `ltr` or `rtl`, is documented as affecting dragging behaviour, which is a reminder that dragging is the most orientation-sensitive part of a tree.

## Async loading has a loop to avoid

Loading children on demand is supported through `loadData`, a function of the node whose return value should be a promise, paired with `loadedKeys` to mark which nodes are loaded and an `onLoad` callback that fires when a node is loaded. The documentation flags the trap directly: if you set `loadedKeys`, you must handle `onLoad` to avoid an infinite loop. That is a controlled-component trap in its purest form, because the component tells you a node is loaded and your handler has to tell the component the node is loaded, and if the second half is missing the two sides disagree forever. The `filterTreeNode` prop lets you hide nodes with a predicate, and `loadData` plus `filterTreeNode` is the standard shape for a lazy tree.

## Virtual scrolling and the thousand node rule

Large trees are handled through virtual scrolling, which comes from the separate `@rc-component/virtual-list` package, and the `virtual` prop disables it when set to false. The prop table also carries a practical warning that is worth more than a benchmark: if you have a lot of TreeNodes, more than 1000, make the parent node collapsed by default, and the effect is obvious and very fast, because the children are hidden. That is an unusual thing for a component library to document, and it tells you the real constraint is not the rendering but the data model, since a fully expanded tree with a thousand leaves is a thousand React elements in the tree, virtualised or not. The prop table also exposes `expandAction` for the open logic, `false`, `click` or `doubleClick`, and the `icon` and `switcherIcon` props for the chevrons and node icons.

## The build and the surrounding tooling

The package is published as `@rc-component/tree` at version 1.5.3, with a CommonJS entry at `lib/index` and an ES module entry at `es/index`, and the published files are the assets, the es build and the lib build. The runtime dependencies are small and all in the same family: `@rc-component/motion` for the animations, `@rc-component/util` for the shared helpers, `@rc-component/virtual-list` for the scrolling, and `clsx` for class name composition. The build uses father and a lessc step to turn the LESS assets into CSS, the tests run through `rc-test` with a coverage variant, the docs are built with dumi and deployed to GitHub Pages, and the local documentation site runs with `npm install` then `npm start` on port 8000. The repository also has an `assets/` directory, an `update-example.js` script, both English and Simplified Chinese readmes, and a MIT licence.

## Conclusion

Adopt @rc-component/tree if you are building a file picker, a permissions tree or any hierarchical selector in React, since it handles the hard parts of tree interaction, keyboard handling and virtualisation, and it is the same component Ant Design itself uses. Do not adopt it if you need a server-driven tree with thousands of loaded nodes, because the documented guidance is to keep parent nodes collapsed by default, which means the data volume per tree is the limit. Verify first which props you need controlled rather than defaulted, since setting a controlled prop disables the matching default one, and check the accessibility behaviour with a keyboard before you ship it.

## FAQ

### How do I install the Ant Design tree component?

Run `npm install @rc-component/tree`, and import the stylesheet with `import '@rc-component/tree/assets/index.css'`. The package also publishes a CommonJS build at `lib/index` and an ES module build at `es/index`.

### What is the difference between expandedKeys and defaultExpandedKeys?

One is controlled and the other is not. After you set a controlled prop such as `expandedKeys`, the matching default prop will not work, so you choose one mode per behaviour rather than mixing them.

### How does checkStrictly change the checkedKeys type?

Normally parent and children nodes are associated, so checking a parent checks its children. When `checkStrictly` is set alongside `checkable`, the value should be an object containing a checked array and a halfChecked array instead of a plain array of keys.

### How do I load tree data asynchronously?

Use `loadData`, a function of the node that returns a promise, together with `loadedKeys` to mark loaded nodes and an `onLoad` callback. The documentation warns that if you set `loadedKeys` you must handle `onLoad` to avoid an infinite loop.

### Does the Ant Design tree handle large node counts?

It uses virtual scrolling through `@rc-component/virtual-list`, and the `virtual` prop disables it. The documentation also advises that with more than 1000 nodes you should keep parent nodes collapsed by default, since the effect is obvious and very fast.

## Sources

- [License: MIT](https://github.com/react-component/tree/blob/master/LICENSE)
- [Project website](https://react-component.github.io/tree)
- [react-component/tree on GitHub](https://github.com/react-component/tree)
- [README](https://github.com/react-component/tree/blob/master/README.md)
- [Releases](https://github.com/react-component/tree/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/react-component-tree
