@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
1,738 lines (1,616 loc) • 80.9 kB
Markdown
---
localeCode: en-US
order: 61
category: Navigation
title: Tree
subTitle: Tree
icon: doc-tree
brief: A tree view component presents a hierarchical list.
---
## Demos
### How to import
```jsx
import { Tree } from '@douyinfe/semi-ui';
```
### Basic Usage
By default, tree is in single select mode and each item is selectable.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree treeData={treeData} defaultExpandAll style={style} />
);
};
```
### Multi-choice
You could use `multiple` to set mode to multi-choice. When all child items are selected, the parent item will be selected.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree treeData={treeData} multiple defaultExpandAll style={style} />
);
};
```
### Searchable
Use `filterTreeNode` to support search input. By default it searches the `label` property of the data. You can use `treeNodeFilterProp` to set another property to search or pass in a function to `filterTreeNode` to customize search behavior.
You could also use `showFilteredOnly` if you prefer to display filtered results only.
```jsx live=true
import React from 'react';
import { Tree, Switch } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
showFilteredOnly: false,
};
this.onChange = this.onChange.bind(this);
}
onChange(showFilteredOnly) {
this.setState({ showFilteredOnly });
}
render() {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
const { showFilteredOnly } = this.state;
return (
<>
<span>showFilteredOnly</span>
<Switch
checked={showFilteredOnly}
onChange={this.onChange}
size="small"
/>
<br/>
<Tree
treeData={treeData}
multiple
filterTreeNode
showFilteredOnly={showFilteredOnly}
style={style}
/>
</>
);
}
}
```
After setting the `filterTreeNode` property to enable search, you can customize the rendering method of the search box by setting `searchRender`. When set to `false`, the search box can be hidden.
```jsx live=true
import React from 'react';
import { Tree, Input } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
return (
<Tree
filterTreeNode
searchRender={({ prefix, ...restProps }) => (
<Input
prefix='Search'
{...restProps}
/>
)}
treeData={treeData}
/>
);
};
```
### Trigger search manually
Use ref to get tree instance,you can call `search` method of tree to trigger search manually. Note that you need to set `filterTreeNode` to enable search at the same time.If the search box is outside the tree, you can hide the search box inside the tree by setting `searchRender=false`.
```jsx live=true
import React from 'react';
import { Tree, Input } from '@douyinfe/semi-ui';
() => {
const ref = useRef();
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0',
},
],
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0',
},
{
label: 'Canada',
value: 'Canada',
key: '1-1',
},
],
},
];
return (
<div>
<Input aria-label='filter tree' prefix="Search" showClear onChange={v => ref.current.search(v)} />
<div style={{ marginTop: 20 }}>search result:</div>
<Tree
ref={ref}
filterTreeNode
searchRender={false}
treeData={treeData}
blockNode={false}
/>
</div>
);
};
```
### JSON TreeData
You could use `treeDataSimpleJson` to pass in `treeNodes` data in JSON format. In this case, key will be used as `key` and `label`, and value will be used as `value` correspondingly. Return value includes JSON data in selected nodes.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const json = {
"Node1": {
"Child Node1": '0-0-1',
"Child Node2": '0-0-2',
},
"Node2": "0-1"
};
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeDataSimpleJson={json}
multiple
onChange={e => console.log('All selected values: ', e)}
onSelect={e => console.log('Current item: ', e)}
style={style}
/>
);
};
```
### BlockNode
You could use `blockNode` to set node to display as a row. In this case, styles for hovering and selected state take effects on the entire row. By default, it is set to `true`. When it is set to `false`, only label will be highlighted.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
return (
<div>
<Tree
treeData={treeData}
defaultValue='Shanghai'
blockNode={false}
/>
<br/>
<Tree
treeData={treeData}
defaultValue='Shanghai'
multiple
blockNode={false}
/>
</div>
);
};
```
### Custom TreeNode Label
You could pass in ReactNode for `label` in `TreeNodeData` to customize label. Pay attention that by default `filterTreeNode` searches data by label. When label is a ReactNode, it is advised to pass in customized search function for a searchable tree.
```jsx live=true
import React from 'react';
import { Tree, ButtonGroup, Button } from '@douyinfe/semi-ui';
() => {
let opts = {
content: 'Hi, Bytedance dance dance',
duration: 3,
};
const button = (
<ButtonGroup
size="small"
theme="borderless"
>
<Button
onClick={e => {
Toast.info(opts);
e.stopPropagation();
}}
>Alert</Button>
<Button>Click</Button>
</ButtonGroup>
);
const style = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
};
const treeDataWithNode = [
{
label: (
<div style={style}>
<span>Asia</span>
{button}
</div>
),
value: 'Asia',
key: 'Asia',
children: [
{
label: (
<div style={style}>
<span>China</span>
{button}
</div>
),
value: 'China',
key: 'China'
},
{
label: (
<div style={style}>
<span>Japan</span>
{button}
</div>
),
value: 'Japan',
key: 'Japan',
},
],
}
];
const treeStyle = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeData={treeDataWithNode}
style={treeStyle}
/>
);
};
```
### Custom Icon
You could use `icon` to add customized icon.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
import { IconMapPin } from '@douyinfe/semi-icons';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
icon: (<IconMapPin style={{ color: 'var(--semi-color-text-2)' }}/>),
children: [
{
label: 'China',
value: 'China',
key: '0-0',
icon: (<IconMapPin style={{ color: 'var(--semi-color-text-2)' }}/>)
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
icon: (<IconMapPin style={{ color: 'var(--semi-color-text-2)' }}/>)
},
],
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeData={treeData}
style={style}
/>
);
};
```
### Directory
You could use `directory` to display tree as a directory with default icons. Also, the icon could be overwritten by custom Icon.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeData={treeData}
directory
style={style}
/>
);
};
```
### Disabled
You can use `disableStrictly` to set whether to enable strict disabling. After enabling strict disabling, when the node is disabled, the selected state cannot be changed through the relationship between the child or the parent.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
disabled: true,
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
disabled: true,
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeData={treeData}
defaultValue='Shanghai'
multiple
style={style}
disableStrictly
/>
);
};
```
### Checked RelationShip
Version: >= 2.5.0
When multiple selections are made, `checkRelation` can be used to set the type of node selection relationship, optional: 'related' (default), 'unRelated'. When the selection relationship is 'unRelated', it means that selections between nodes do not affect each other.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
{
label: 'Chengdu',
value: 'Chengdu',
key: '0-0-2',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeData={treeData}
multiple
checkRelation='unRelated'
defaultExpandAll
style={style}
/>
);
};
```
### Default Expand All
Both `defaultExpandAll` and `expandAll` can set the default expanded/collapsed state of `Tree`. The difference between the two is that `defaultExpandAll` only takes effect at initialization, while `expandAll` will not only take effect at initialization, but also when the data (`treeData`/`treeDataSimpleJson`) is dynamically updated, `expandAll` will still take effect.
Among them, `expandAll` is supported starting from 1.30.0.
In the demo below, after clicking the button to update `TreeData`, `defaultExpandAll` becomes invalid, and `expandAll` still takes effect.
```jsx live=true dir="column"
import React, { useState } from 'react';
import { Tree, Button } from '@douyinfe/semi-ui';
() => {
const json = {
"Node1": {
"Child Node1": '0-0-1',
"Child Node2": '0-0-2',
},
"Node2": "0-1"
};
const json2 = {
"Node3": {
"Child Node1": '0-0-1',
"Child Node2": '0-0-2',
"Child Node3": '0-0-3',
"Child Node4": '0-0-4',
},
"Node2": "0-1",
};
const style = {
marginRight: 20,
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)',
};
const [tree, setTree] = useState(json);
const handleClick = () => {
setTree(json2);
};
return (
<>
<Button onClick={handleClick} style={{ marginBottom: 10 }}>
Click to update TreeData
</Button>
<div style={{ display: 'flex' }}>
<div>
<span>defaultExpandAll</span>
<Tree
defaultExpandAll
treeDataSimpleJson={tree}
style={style}
/>
</div>
<div>
<span>expandAll</span>
<Tree
expandAll
treeDataSimpleJson={tree}
style={style}
/>
</div>
</div>
</>
);
};
```
### Controlled Expansion with Search
When `expandedKeys` is passed in, it is the expanded controlled component, which can be used with `onExpand`. When the expansion is controlled, if you enable `filterTreeNode` and search, the node will not be automatically expanded. At this time, the expansion of the node is completely controlled by `expandedKeys`. You can use the input parameter `filteredExpandedKeys` (version: >= 2.38.0) of `onSearch` to realize the search expansion effect when the expansion is controlled.
```jsx live=true hideInDSM
import React, { useState } from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const [expandedKeys, setExpandedKeys] = useState([]);
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
}
];
return (
<Tree
style={{ width: 300 }}
treeData={treeData}
filterTreeNode
expandedKeys={expandedKeys}
onExpand={expandedKeys => {
setExpandedKeys(expandedKeys);
}}
onSearch={(inputValue, filteredExpandedKeys) => {
setExpandedKeys([...filteredExpandedKeys]);
}}
/>
);
};
```
### Controlled Component
You can use `value` along with `onChange` property if you want to use Tree as a controlled component.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
value: 'Shanghai'
};
}
onChange(value) {
this.setState({ value });
}
render() {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
treeData={treeData}
value={this.state.value}
onChange={value => this.onChange(value)}
style={style}
/>
);
}
}
```
### Auto Expand Parent
In the case of controlled expansion, when `autoExpandParent` is turned on, if you want to collapse the parent element, you need to collapse all its child elements. By default, `autoExpandParent` is false, that is, the collapse of the parent element is not affected by the child element.
```jsx live=true
import React from 'react';
import { Tree } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
expandedKeys: ['0', '0-0']
};
}
onExpand(value) {
this.setState({ expandedKeys: value });
}
render() {
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
}
];
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<Tree
autoExpandParent
treeData={treeData}
onExpand={v=>this.onExpand(v)}
expandedKeys={this.state.expandedKeys}
style={style}
/>
);
}
}
```
### Custom expansion icon
The expansion Icon can be customized through `expandIcon`. Supports passing in ReactNode or functions. `expandIcon` is supported since 2.75.0.
```ts
expandIcon: ReactNode | ((props: {
onClick: (e: MouseEvent) => void;
className: string;
expanded: boolean;
}))
```
Examples are as follows:
```jsx live=true
() => {
const treeData = [
{
label: 'Asia',
key: 'Asia',
children: [
{
label: 'China',
key: 'China',
children: [
{
label: 'Beijing',
key: 'Beijing',
},
{
label: 'Shanghai',
key: 'Shanghai',
},
],
},
],
},
{
label: 'North America',
value: 'North America',
}
];
const expandIconFunc = useCallback((props) => {
const { expanded, onClick, className } = props;
if (expanded) {
return <IconMinus size="small" className={className} onClick={onClick}/>
} else {
return <IconPlus size="small" className={className} onClick={onClick}/>
}
});
const style = {
width: 260,
height: 200,
border: '1px solid var(--semi-color-border)'
};
return (
<>
<p>ReactNode type</p>
<Tree
style={{ width: 300}}
expandIcon={<IconChevronDown size="small" className='testCls'/>}
multiple
defaultExpandedKeys={['Asia']}
treeData={treeData}
style={style}
/>
<br />
<p>Function type</p>
<Tree
style={{ width: 300}}
multiple
expandIcon={expandIconFunc}
defaultExpandedKeys={['Asia']}
treeData={treeData}
style={style}
/>
</>
);
}
```
### Tree with line
Set the line between nodes through `showLine`, the default is false, supported starting from 2.50.0
```jsx live=true hideInDSM
import React, { useState, useCallback } from 'react';
import { Tree, Switch } from '@douyinfe/semi-ui';
() => {
const [show, setShow] = useState(true);
const onChange = useCallback((value) => {
setShow(value);
}, []);
const treeData = useMemo(() => {
return [
{
label: 'parent-0',
key: 'parent-0',
children: [
{
label: 'leaf-0-0',
key: 'leaf-0-0',
children: [
{
label: 'leaf-0-0-0',
key: 'leaf-0-0-0',
},
{
label: 'leaf-0-0-1',
key: 'leaf-0-0-1',
},
{
label: 'leaf-0-0-2',
key: 'leaf-0-0-2',
},
]
},
{
label: 'leaf-0-1',
key: 'leaf-0-1',
}
]
},
{
label: 'parent-1',
key: 'parent-1',
}
];
}, []);
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<>
<div style={{ display: 'flex', alignItems: 'center', columnGap: 5, marginBottom: 5 }}>
<strong>showLine </strong>
<Switch checked={show} onChange={onChange} />
</div>
<Tree
showLine={show}
defaultExpandAll
treeData={treeData}
style={style}
/>
</>
);
};
```
### Virtualized Tree
If you need to render large sets of tree structured data, you could use virtualized tree. In virtualized mode, animation / motion is disabled for better performance.
The property `virtualize` is an object consisting of the following values:
- height: Height of the tree, If passed in as a string, computed height should not be zero for render purpose, in other words, parent node should have offsetHeight
- width: Width of the tree.
- itemSize: Height for each line of treeNode, required
If tree is searchable, you could also set `showFilteredOnly={true}` to reduce time of rendering for results.
```jsx live=true
import React from 'react';
import { Tree, Button } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
gData: [],
total: 0,
};
this.onGen = this.onGen.bind(this);
}
generateData(x = 5, y = 4, z = 3, gData = []) {
// x:number of nodes
// y:number of nodes with children in each level
// z:number of level
function _loop(_level, _preKey, _tns) {
const preKey = _preKey || '0';
const tns = _tns || gData;
const children = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ label: `${key}-label`, key: `${key}-key`, value: `${key}-value` });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const __level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return _loop(__level, key, tns[index].children);
});
return null;
}
_loop(z);
function calcTotal(x, y, z) {
const rec = n => (n >= 0 ? x * y ** n-- + rec(n) : 0);
return rec(z + 1);
}
return { gData, total: calcTotal(x, y, z) };
}
onGen() {
const { gData, total } = this.generateData();
this.setState({
gData,
total
});
};
render() {
const style = {
width: 260,
// height: 360,
border: '1px solid var(--semi-color-border)'
};
return (
<div style={{ padding: '0 20px' }}>
<Button onClick={this.onGen}>Generate Data: </Button>
<span>In total: {this.state.total}</span>
<br/>
<br/>
{this.state.gData.length ? (
<Tree
treeData={this.state.gData}
filterTreeNode
style={style}
showFilteredOnly
virtualize={{
// if set height for tree, it will fill 100%
height: 300,
itemSize: 28,
}}
/>
) : null}
</div>
);
}
}
```
### Dynamic Update of Data
```jsx live=true
import React from 'react';
import { Tree, Button } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
treeData: [{
key: '0',
label: 'item-0',
value: '0'
}],
};
this.add = this.add.bind(this);
}
add() {
let itemLength = Math.floor(Math.random() * 5) + 1;
let treeData = new Array(itemLength).fill(0).map((v, i) => {
let length = Math.floor(Math.random() * 3);
let children = new Array(length).fill(0).map((cv, ci) => {
let child = {
key: `${i}-${ci}`,
label: `Leaf-${i}-${ci}`,
value: `${i}-${ci}`
};
return child;
});
let item = {
key: `${i}`,
label: `Item-${i}`,
value: `${i}`,
children
};
return item;
});
this.setState({ treeData });
}
render() {
const { treeData } = this.state;
const style = {
width: 260,
height: 420,
border: '1px solid var(--semi-color-border)'
};
return (
<div style={style}>
<Tree
treeData={this.state.treeData}
/>
<br/>
<Button onClick={this.add} style={{ margin: 20 }}>
Update Data
</Button>
</div>
);
}
}
```
### Load Data Asynchronously
You could use `loadData` to load treeData asynchronously on node expansion. Notice `isLeaf` is required to mark node as leaf in treeData.
```jsx live=true
import React, { useState } from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const initialData = [
{
label: 'Expand to load',
value: '0',
key: '0',
},
{
label: 'Expand to load',
value: '1',
key: '1',
},
{
label: 'Leaf Node',
value: '2',
key: '2',
isLeaf: true,
},
];
const [treeData, setTreeData] = useState(initialData);
function updateTreeData(list, key, children) {
return list.map(node => {
if (node.key === key) {
return { ...node, children };
}
if (node.children) {
return { ...node, children: updateTreeData(node.children, key, children) };
}
return node;
});
}
function onLoadData({ key, children }) {
return new Promise(resolve => {
if (children) {
resolve();
return;
}
setTimeout(() => {
setTreeData(origin =>
updateTreeData(origin, key, [
{
label: 'Child Node',
key: `${key}-0`,
},
{
label: 'Child Node',
key: `${key}-1`,
},
]),
);
resolve();
}, 1000);
});
}
return <Tree loadData={onLoadData} treeData={[...treeData]} />;
};
```
### Draggable Tree
You could use `draggable` along with `onDrop` to achieve a draggable Tree.
<Notice title='Notice'>
Drag and drop is available since v 1.8.0. Simultaneous use with virtualization is currently not supported
</Notice>
The callback input parameters of the drag event are as follows:
```
- onDragEnd: function({ event, node: DragTreeNode })
- onDragEnter:function({ event, node: DragTreeNode, expandedKeys: string[] })
- onDragLeave:function({ event, node: DragTreeNode })
- onDragOver:function({ event, node: DragTreeNode })
- onDragStart: function({ event, node: DragTreeNode })
- onDrop:function({ event, node: DragTreeNode, dragNode: DragTreeNode, dragNodesKeys: string[], dropPosition: number, dropToGap: Boolean })
```
The data type DragTreeNode, in addition to all the attributes of TreeNodeData, also includes expanded and pos attributes,
```
DragTreeNode {
expanded: Boolean,
pos: string
value?: string | number;
label?: React.ReactNode;
disabled?: boolean;
isLeaf?: boolean;
[key: string]: any;
}
```
- `pos` refers to the positional relationship of the current node in the entire treeData, such as the 0th node of the 2nd node of the 1st node of the 0th layer: '0-1-2-0'
- `dropPosition` refers to the position where the dragged node is dropped in the current hierarchy. If it is inserted before the 0th node of the same level, it will be -1, and after the 0th node, it will be 1, and it will fall on it. is 0, and so on. With dropToGap, a more complete judgment can be obtained.
- `dropToGap` refers to whether the dragged node is dropped between nodes, if false, it is dropped above a node
```jsx live=true
import React, { useState } from 'react';
import { Tree } from '@douyinfe/semi-ui';
() => {
const initialData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
{
label: 'Japan',
value: 'Japan',
key: '0-1',
children: [
{
label: 'Osaka',
value: 'Osaka',
key: '0-1-0'
}
]
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
children: [
{
label: 'United States',
value: 'United States',
key: '1-0'
},
{
label: 'Canada',
value: 'Canada',
key: '1-1'
}
]
},
{
label: 'Europe',
value: 'Europe',
key: '2',
}
];
const [treeData, setTreeData] = useState(initialData);
function onDrop(info) {
const { dropToGap, node, dragNode } = info;
const dropKey = node.key;
const dragKey = dragNode.key;
const dropPos = node.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const data = [...treeData];
const loop = (data, key, callback) => {
data.forEach((item, ind, arr) => {
if (item.key === key) return callback(item, ind, arr);
if (item.children) return loop(item.children, key, callback);
});
};
let dragObj;
loop(data, dragKey, (item, ind, arr) => {
arr.splice(ind, 1);
dragObj = item;
});
if (!dropToGap) {
// inset into the dropPosition
loop(data, dropKey, (item, ind, arr) => {
item.children = item.children || [];
item.children.push(dragObj);
});
} else if (dropPosition === 1 && node.children && node.expanded) {
// has children && expanded and drop into the node bottom gap
// could insert anywhere. Here we insert to the top.
loop(data, dropKey, item => {
item.children = item.c