Having a tree structure on both sides of the Transfer component

All we need is an easy explanation of the problem, so here it is.

I am using antd’s Transfer component. Using the examples given in the documentation, I am able to create a tree transfer box that looks similar to:
enter image description here

Is there a way I could have a tree structure also on the right side? Currently, as I select 0-1-0 under 0-1, it appears flat on the right side.

The code as also given in the Sandbox example, is given below:

import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Transfer, Tree } from 'antd';

const { TreeNode } = Tree;

// Customize Table Transfer
const isChecked = (selectedKeys, eventKey) => {
    return selectedKeys.indexOf(eventKey) !== -1;
};

const generateTree = (treeNodes = [], checkedKeys = []) => {
    return treeNodes.map(({ children, ...props }) => (
        <TreeNode {...props} disabled={checkedKeys.includes(props.key)}>
        {generateTree(children, checkedKeys)}
        </TreeNode>
    ));
};

const TreeTransfer = ({ dataSource, targetKeys, ...restProps }) => {
    const transferDataSource = [];
    function flatten(list = []) {
        list.forEach(item => {
        transferDataSource.push(item);
        flatten(item.children);
        });
    }
    flatten(dataSource);

    return (
        <Transfer
        {...restProps}
        targetKeys={targetKeys}
        dataSource={transferDataSource}
        className="tree-transfer"
        render={item => item.title}
        showSelectAll={false}
        >
        {({ direction, onItemSelect, selectedKeys }) => {
            if (direction === 'left') {
            const checkedKeys = [...selectedKeys, ...targetKeys];
            return (
                <Tree
                blockNode
                checkable
                checkStrictly
                defaultExpandAll
                checkedKeys={checkedKeys}
                onCheck={(
                    _,
                    {
                    node: {
                        props: { eventKey },
                    },
                    },
                ) => {
                    onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                }}
                onSelect={(
                    _,
                    {
                    node: {
                        props: { eventKey },
                    },
                    },
                ) => {
                    onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                }}
                >
                {generateTree(dataSource, targetKeys)}
                </Tree>
            );
            }
        }}
        </Transfer>
    );
};

const treeData = [
    { key: '0-0', title: '0-0' },
    {
        key: '0-1',
        title: '0-1',
        children: [{ key: '0-1-0', title: '0-1-0' }, { key: '0-1-1', title: '0-1-1' }],
    },
    { key: '0-2', title: '0-3' },
];

class App extends React.Component {
    state = {
        targetKeys: [],
    };

    onChange = targetKeys => {
        console.log('Target Keys:', targetKeys);
        this.setState({ targetKeys });
    };

    render() {
        const { targetKeys } = this.state;
        return (
        <div>
            <TreeTransfer dataSource={treeData} targetKeys={targetKeys} onChange={this.onChange} />
        </div>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('container'));

The way I want to transfer is depicted in the images below.

  • I want to be able to transfer the children
  • When a child is transferred the left table should have the remaining children under it parent and the right table should have the transferred children under it parent name.

enter image description here

enter image description here

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Check out this example where:

  • TransferTree is an example of checking nodes and showing the tree path.
  • AntdTreeTransfer an example for Transfer component with tree path on transfer.

enter image description here

export const renderTreeNodes = data =>
  data.map(item =>
    item.children ? (
      <Tree.TreeNode title={item.title} key={item.key} dataRef={item}>
        {renderTreeNodes(item.children)}
      </Tree.TreeNode>
    ) : (
      <Tree.TreeNode {...item} dataRef={item} />
    )
  );

export const filterTree = (keys, halfKeys, rootNode) =>
  rootNode
    ? rootNode
        .filter(node => keys.includes(node.key) || halfKeys.includes(node.key))
        .map(nodeRoot => ({
          ...nodeRoot,
          children: filterTree(keys, halfKeys, nodeRoot.children)
        }))
    : [];

export default function TreeTransfer() {
  const [checkedNodes, setCheckedNodes] = useState([]);

  const onCheck = (selectedKeys, info) => {
    const filteredTree = filterTree(selectedKeys, info.halfCheckedKeys, data);
    setCheckedNodes(filteredTree);
  };

  return (
    <FlexBox>
      <Row type="flex" gutter={20}>
        <Col>
          <Card>
            <Tree checkable defaultExpandAll onCheck={onCheck}>
              {renderTreeNodes(data)}
            </Tree>
          </Card>
        </Col>
        <Col>
          <Card>
            <Tree checkable defaultExpandAll>
              {renderTreeNodes(checkedNodes)}
            </Tree>
          </Card>
        </Col>
      </Row>
    </FlexBox>
  );
}

export default function AntdTreeTransfer() {
  const [leftCheckedKeys, setLeftCheckedKeys] = useState([]);
  const [checkedNodes, setCheckedNodes] = useState([]);
  const [targetNodes, setTargetNodes] = useState([]);

  return (
    <FlexBox>
      <Transfer
        operations={['', 'Clear']}
        onChange={(_, direction) => {
          setLeftCheckedKeys([]);

          direction === 'right'
            ? setTargetNodes(checkedNodes)
            : setTargetNodes([]);
        }}
        style={{ width: '50vh' }}
      >
        {({ direction, onItemSelect, selectedKeys }) =>
          direction === 'left' ? (
            <Tree
              showLine
              blockNode
              checkable
              defaultExpandAll
              checkedKeys={leftCheckedKeys}
              onCheck={(selectedKeys, info) => {
                setLeftCheckedKeys(selectedKeys);
                const filteredTree = filterTree(
                  selectedKeys,
                  info.halfCheckedKeys,
                  data
                );
                setCheckedNodes(filteredTree);

                const eventKey = info.node.props.eventKey;
                onItemSelect(eventKey, selectedKeys.includes(eventKey));
              }}
            >
              {renderTreeNodes(data)}
            </Tree>
          ) : (
            <Tree
              autoExpandParent
              blockNode
              checkable
              onCheck={(selectedKeys, info) => {
                const eventKey = info.node.props.eventKey;
                onItemSelect(eventKey, selectedKeys.includes(eventKey));
              }}
            >
              {renderTreeNodes(targetNodes)}
            </Tree>
          )
        }
      </Transfer>
    </FlexBox>
  );
}

Edit Q-56680158-TreeTransfer

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply