Need to fix a issue that comming in antd tree component

Viewed 463

I am using an antd tree component my issue is if you search something in the search bar then you will get search results in that result if you check and up check any field what happens all previous data get unchecked whatever data is present into the search bar result that only data remain selected if it already select or you just select what my task is I don't want to get unchecked all previously selected checked that only field update that we change right now I don't have any idea how can I fix this if anybody knows anyway, also I added a complete code SandBox link below.

This is my search bar filter code

const hasSearchTerm = (n, searchTerm) =>
  n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm) =>
  arr?.filter(
    (n) =>
      hasSearchTerm(n.title, searchTerm) ||
      filterData(n.children, searchTerm)?.length > 0
  );

function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
  let keysToExpand = [];
  const filteredData = searchString
    ? filterData(data, searchString).map((n) => {
        keysToExpand.push(n.key);
        return {
          ...n,
          children: filterData(n.children, searchString, checkedKeys)
        };
      })
    : data;
  setExpandedTree([...keysToExpand]);
  return filteredData;
}

This issue happens when the check or unchecks field after searching in the search bar in this part of the code

const onCheck = (checkedKeysValue) => {
    console.log("onCheck", checkedKeysValue);
    setCheckedKeys(checkedKeysValue);
  };
const Demo = () => {
  const [expandedKeys, setExpandedKeys] = useState([]);
  const [checkedKeys, setCheckedKeys] = useState([]);
  const [selectedKeys, setSelectedKeys] = useState([]);
  const [autoExpandParent, setAutoExpandParent] = useState(true);
  const [searchValue, setSearchValue] = useState("");
  const [tree, setTree] = useState(treeData);

  const onExpand = (expandedKeysValue) => {
    console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
    // or, you can remove all expanded children keys.

    setExpandedKeys(expandedKeysValue);
    setAutoExpandParent(false);
  };

  const onCheck = (checkedKeysValue) => {
    console.log("onCheck", checkedKeysValue);
    setCheckedKeys(checkedKeysValue);
  };

  const onSelect = (selectedKeysValue, info) => {
    console.log("onSelect", info);
    setSelectedKeys(selectedKeysValue);
  };
  React.useEffect(() => {
    const checked = [];

    treeData.forEach((data) => {
      data.children.forEach((item) => {
        if (item.checked) {
          checked.push(item.key);
        }
      });
    });
    setCheckedKeys(checked);
  }, []);
  React.useEffect(() => {
    if (searchValue) {
      const filteredData = filteredTreeData(
        treeData,
        searchValue,
        checkedKeys,
        setExpandedKeys
      );
      setTree([...filteredData]);
    } else {
      setTree(treeData);
      // setExpandedKeys([]);
    }
  }, [searchValue, checkedKeys]);

  return (
    <div>
      <Search
        style={{ marginBottom: 8 }}
        placeholder="Search"
        onChange={(e) => {
          setSearchValue(e.target.value);
        }}
      />
      <Tree
        checkable
        onExpand={onExpand}
        expandedKeys={expandedKeys}
        autoExpandParent={autoExpandParent}
        onCheck={onCheck}
        checkedKeys={checkedKeys}
        onSelect={onSelect}
        selectedKeys={selectedKeys}
        treeData={tree}
      />
    </div>
  );
};

CodeSandBox Link

2 Answers

Edited Response to fix uncheck issue

If I understood the question correctly, Is the issue with the fact that previous checked items are getting cleared on search and selection of new one?

I think the solution would be to use 2 different separate trees one for filtered and the other for normal.

Did some code changes on top of the sandbox code shared.

  1. I have added a new tree when searchedValue is present.
  2. On checking/unchecking the new filtered tree, the actual entire tree's checked values get updated
  3. When the searched value is empty it would show the actual entire tree
  4. Created a filteredKeys list to solve uncheck issue. Now I am able to select and unselect.

If you play around and refactor a bit, you should be able to achieve what you want.

Adding the same code below.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";

const { Search } = Input;
const treeData = [
  {
    title: "AP Watchlists",
    key: "AP Watchlists",
    children: [
      { title: "Colo Open Data", key: "Colo Open Data", checked: true },

      {
        title: "Department and trade",
        key: "Department and trade",
        checked: true
      },
      {
        title: "North List",
        key: "North List",
        checked: true
      },
      { title: "People's Daily", key: "People's Daily", checked: true }
    ]
  },
  {
    title: "Af Watchlists",
    key: "Af Watchlists",
    children: [
      {
        title: "Service Wanted List",
        key: "Service Wanted List",
        checked: true
      }
    ]
  },
  {
    title: "EM Watchlists",
    key: "EM Watchlists",
    children: [
      {
        title: "National Financing",
        key: "National Financing",
        checked: true
      },
      {
        title: "Arabia List",
        key: "Arabia List",
        checked: true
      }
    ]
  },

  {
    title: "Assets List",
    key: "Assets List",
    children: [
      {
        title: "National List",
        key: "National List",
        checked: true
      }
    ]
  },
  {
    title: "New Watchlists",
    key: "New Watchlists",
    children: [
      { title: "FATR", key: "FATR", checked: true },
      { title: "Internal", key: "Internal", checked: true },
      {
        title: "OC List (Covers 73 Lists)",
        key: "OC List (Covers 73 Lists)",
        checked: true
      },
      { title: "UN", key: "UN", checked: true },
      {
        title: "Security List (Covers 18 Lists)",
        key: "Security List (Covers 18 Lists)",
        checked: true
      }
    ]
  }
];

const Demo = () => {
  const hasSearchTerm = (n, searchTerm) =>
    n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
  const filterData = (arr, searchTerm, keys) => {
    const result = arr?.filter(
      (n) =>
        hasSearchTerm(n.title, searchTerm) ||
        filterData(n.children, searchTerm, keys)?.length > 0
    );
    result &&
      result.forEach((node) => {
        if (keys.indexOf(node?.key) === -1) keys.push(node?.key);
      });
    return result;
  };

  function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
    let keysToExpand = [];
    const keys = [];
    const filteredData = searchString
      ? filterData(data, searchString, keys).map((n) => {
          keysToExpand.push(n.key);
          return {
            ...n,
            children: filterData(n.children, searchString, keys)
          };
        })
      : data;
    setExpandedTree([...keysToExpand]);
    setFilteredKeys(keys);
    return filteredData;
  }

  const [expandedKeys, setExpandedKeys] = useState([]);
  const [checkedKeys, setCheckedKeys] = useState([]);
  const [filteredCheckedKeys, setFilteredCheckedKeys] = useState([]);
  const [selectedKeys, setSelectedKeys] = useState([]);
  const [autoExpandParent, setAutoExpandParent] = useState(true);
  const [searchValue, setSearchValue] = useState("");
  const [tree, setTree] = useState(treeData);
  const [filteredTree, setFilteredTree] = useState([]);
  const [filteredKeys, setFilteredKeys] = useState([]);

  const onExpand = (expandedKeysValue) => {
    // console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
    // or, you can remove all expanded children keys.

    setExpandedKeys(expandedKeysValue);
    setAutoExpandParent(false);
  };

  const onCheck = (checkedKeysValue) => {
    // console.log("onCheck", checkedKeysValue);
    // console.log("checkedKeys", checkedKeys);
    setCheckedKeys(checkedKeysValue);
  };

  const onFilteredTreeCheck = (checkedKeysValue) => {
    // console.log("onFilterCheck", checkedKeysValue);
    // console.log("filteredcheckedKeys", filteredCheckedKeys);
    setFilteredCheckedKeys(checkedKeysValue);
    const baseTreeKeys = [...checkedKeys].filter(
      (node) => filteredKeys.indexOf(node) === -1
    );
    console.log("baseTreeKeys", baseTreeKeys);
    console.log("checkedKeysValue", checkedKeysValue);
    setCheckedKeys([...checkedKeysValue, ...baseTreeKeys]);
  };

  const onSelect = (selectedKeysValue, info) => {
    console.log("onSelect", info);
    setSelectedKeys(selectedKeysValue);
  };
  // React.useEffect(() => {
  //   const checked = [];

  //   treeData.forEach((data) => {
  //     data.children.forEach((item) => {
  //       if (item.checked) {
  //         checked.push(item.key);
  //       }
  //     });
  //   });
  //   setCheckedKeys(checked);
  // }, []);
  React.useEffect(() => {
    setFilteredKeys([]);
    if (searchValue) {
      const filteredData = filteredTreeData(
        treeData,
        searchValue,
        checkedKeys,
        setExpandedKeys
      );
      setFilteredTree([...filteredData]);
    } else {
      setTree(treeData);
      // setExpandedKeys([]);
    }
  }, [searchValue, checkedKeys]);

  console.log("filteredCHeckedValues", filteredCheckedKeys);
  console.log("existing checked values", checkedKeys);

  return (
    <div>
      <Search
        style={{ marginBottom: 8 }}
        placeholder="Search"
        onChange={(e) => {
          setSearchValue(e.target.value);
        }}
      />
      {searchValue ? (
        <Tree
          checkable
          onExpand={onExpand}
          expandedKeys={expandedKeys}
          autoExpandParent={autoExpandParent}
          onCheck={onFilteredTreeCheck}
          checkedKeys={filteredCheckedKeys}
          onSelect={onSelect}
          selectedKeys={selectedKeys}
          treeData={filteredTree}
        />
      ) : (
        <Tree
          checkable
          onExpand={onExpand}
          expandedKeys={expandedKeys}
          autoExpandParent={autoExpandParent}
          onCheck={onCheck}
          checkedKeys={checkedKeys}
          onSelect={onSelect}
          selectedKeys={selectedKeys}
          treeData={tree}
        />
      )}
    </div>
  );
};

ReactDOM.render(<Demo />, document.getElementById("container"));

Let me know if this solves your issue. I am able to select multiple values in subsequent searches without loosing the checked ones.

As mentioned in the API document, filterTreeNode will keep keys from the tree node, and will not hide it.

filterTreeNode
Defines a function to filter treeNodes. When the function returns true, the corresponding treeNode will be checked

If you want to hide tree node, you will have to manually filter it first before before passing it to Tree in loop function, something like:

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
const gData = [
  {
    key: "1",
    title: "title 1"
  },
  {
    key: "2",
    title: "title 2"
  },
  {
    key: "3",
    title: "title 3",
    children: [
      {
        key: "4",
        title: "title 4"
      },
      {
        key: "5",
        title: "title 5",
        children: [
          {
            key: "6",
            title: "title 6"
          },
          {
            key: "7",
            title: "title 7"
          }
        ]
      }
    ]
  }
];
const { Search } = Input;

const dataList = [];
const generateList = (data) => {
  for (let i = 0; i < data.length; i++) {
    const node = data[i];
    const { key } = node;
    dataList.push({ key, title: key });
    if (node.children) {
      generateList(node.children);
    }
  }
};
generateList(gData);

const getParentKey = (key, tree) => {
  let parentKey;
  for (let i = 0; i < tree.length; i++) {
    const node = tree[i];
    if (node.children) {
      if (node.children.some((item) => item.key === key)) {
        parentKey = node.key;
      } else if (getParentKey(key, node.children)) {
        parentKey = getParentKey(key, node.children);
      }
    }
  }
  return parentKey;
};

const SearchTree = () => {
  const [expandedKeys, setExpandedKeys] = useState([]);
  const [autoExpandParent, setAutoExpandParent] = useState(true);
  const [searchValue, setSearchValue] = useState("");
  const [treeData, setTreeData] = useState(gData);

  const onExpand = (expandedKeys) => {
    setExpandedKeys(expandedKeys);
    setAutoExpandParent(false);
  };

  const onChange = (e) => {
    const value = e.target.value?.toLowerCase();
    const expandedKeys = dataList
      .map((item) => {
        if (item.title.indexOf(value) > -1) {
          return getParentKey(item.key, gData);
        }
        return null;
      })
      .filter((item, i, self) => item && self.indexOf(item) === i);
    if (value) {
      const hasSearchTerm = (n) => n.toLowerCase().indexOf(value) !== -1;
      const filterData = (arr) =>
        arr?.filter(
          (n) => hasSearchTerm(n.title) || filterData(n.children)?.length > 0
        );
      const filteredData = filterData(gData).map((n) => {
        return {
          ...n,
          children: filterData(n.children)
        };
      });

      setTreeData(filteredData);
      setExpandedKeys(expandedKeys);
      setSearchValue(value);
      setAutoExpandParent(true);
    } else {
      setTreeData(gData);
      setExpandedKeys([]);
      setSearchValue("");
      setAutoExpandParent(false);
    }
  };

  const filterTreeNode = (node) => {
    const title = node.title.props.children[2];
    const result = title.indexOf(searchValue) !== -1 ? true : false;
    console.log(searchValue);
    console.log(result);
    return result;
  };

  const loop = (data) => 
    data.map((item) => {
      const index = item.title.indexOf(searchValue);
      const beforeStr = item.title.substr(0, index);
      const afterStr = item.title.substr(index + searchValue.length);
      const title =
        index > -1 ? (
          <span>
            {beforeStr}
            <span className="site-tree-search-value">{searchValue}</span>
            {afterStr}
          </span>
        ) : (
          <span>{item.title}</span>
        );
      if (item.children) {
        return { title, key: item.key, children: loop(item.children) };
      }

      return {
        title,
        key: item.key
      };
    });

  return (
    <div>
      <Search
        style={{ marginBottom: 8 }}
        placeholder="Search"
        onChange={onChange}
      />
      <Tree
        onExpand={onExpand}
        expandedKeys={expandedKeys}
        autoExpandParent={autoExpandParent}
        treeData={loop(treeData)}
        filterTreeNode={filterTreeNode}
      />
    </div>
  );
};

ReactDOM.render(<SearchTree />, document.getElementById("container"));
Related