TextField失去焦点

时间:2020-02-11 15:25:56

标签: reactjs material-ui

我对TreeView和TextField有问题。我将这些组件包装在TreeViewContainer中。我想通过在TextField中键入来过滤树节点。问题是当我选择节点,然后尝试过滤树时。 TextField失去焦点,并且选择了与第一个字符匹配的第一个节点(或有时记录错误)。

Uncaught TypeError: Cannot read property 'parent' of undefined
    at eval (TreeView.js:327)
    at Array.forEach (<anonymous>)
    at setFocusByFirstCharacter (TreeView.js:321)
    at printableCharacter (TreeItem.js:173)
    at handleKeyDown (TreeItem.js:262)
    at HTMLUnknownElement.callCallback (react-dom.development.js:336)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:385)

我想知道这是用户界面错误还是我做错了什么?

Working example

1 个答案:

答案 0 :(得分:2)

TreeView维护focused node id within its state。当您重新渲染具有不同结构的树(例如,删除“根”节点)时,TreeItem将被重新挂载,而不是仅仅被重新渲染。 TreeItem根据TreeView上下文监视whether or not it should be focused,如果更改,它将抓住焦点。安装TreeItem时将执行焦点逻辑。通常,TreeItem不会抓住焦点,但在您的特定情况下,由于TreeView保持一致并记住上一个焦点的TreeItem,但是所有物品都已重新装载,它将焦点重新带到TreeItem

问题的另一方面是TreeView remembers its nodes。它has logic to try to maintain this,但看来您的场景可能正在暴露该逻辑中的错误,并且类型化(一旦焦点又回到树项目上)试图查找不再存在的节点的父级(尽管我需要使用一个更简单的代码示例对此进行更多研究,以确定为什么nodeMap不太正确以及这是否真的是一个错误。)

只要在搜索后设置新树,就可以通过更改TreeView的键来修复焦点偏移和过期的nodeMap信息。这将导致TreeView的重新安装,因此它不再记住上一个聚焦的节点或旧的节点图。

这是您沙盒中代码的修改版本(我的更改由“ Ryan添加”表示):

import React, { useState } from "react";
import TreeViewFilter from "./TreeViewFilter";
import TreeView from "@material-ui/lab/TreeView";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import { Folder } from "@material-ui/icons";
import TreeItem from "@material-ui/lab/TreeItem";
import { makeStyles } from "@material-ui/core/styles";
import { amber } from "@material-ui/core/colors";
import _ from "lodash";

const useStyles = makeStyles({
  root: {
    height: 216,
    flexGrow: 1,
    maxWidth: 400
  }
});

enum NodeType {
  FOLDER = 0,
  SCHEMA
}

type TreeNode = {
  name: string;
  type: NodeType;
  parent: string | null;
  childNodes: Array<TreeNode>;
};

export interface ITreeViewProps {
  tree: TreeNode;
  fetchItem?: () => void;
}

export default function TreeViewContainer(props: ITreeViewProps) {
  const classes = useStyles();

  const [searchValue, setSearchValue] = useState("");
  const [tree, setTree] = useState(props.tree);
  // ** Added by Ryan ** New treeKey state
  const [treeKey, setTreeKey] = useState(1);

  function fetchItem(id: string) {
    // inicjacja pobierania schematu/listy alarmów/ raport/ wykresu
    console.log("Fetch  " + id);
  }

  function renderTree(tree: any) {
    if (Array.isArray(tree)) {
      return tree.map(item => buildTree(item));
    } else {
      return buildTree(tree);
    }
  }

  function buildTree(tree: TreeNode) {
    return (function recursive(currentNode: TreeNode) {
      if (currentNode.type === NodeType.FOLDER) {
        return (
          <TreeItem
            key={currentNode.name}
            nodeId={`${currentNode.type}${currentNode.name}`}
            label={
              <div>
                <Folder style={{ color: amber[500] }} />{" "}
                <span>{currentNode.name}</span>
              </div>
            }
          >
            {currentNode.childNodes.map(node => recursive(node))}
          </TreeItem>
        );
      }

      if (currentNode.type === NodeType.SCHEMA) {
        return (
          <TreeItem
            key={currentNode.name}
            nodeId={`${currentNode.type}${currentNode.name}`}
            onClick={() => {
              fetchItem(currentNode.name);
            }}
            label={currentNode.name + ".sh"}
          />
        );
      }

      return null;
    })(tree);
  }

  const searchTree = (tree: TreeNode, searchValue: string, callback: any) => {
    const searchRE = new RegExp(searchValue, "i");
    return (function recurse(currentNode: TreeNode) {
      for (let i = 0, length = currentNode.childNodes.length; i < length; i++) {
        recurse(currentNode.childNodes[i]);
      }
      if (currentNode.name.match(searchRE))
        callback({
          ...currentNode,
          childNodes: currentNode.childNodes.filter(node =>
            node.name.match(searchRE)
          )
        });
    })(tree);
  };

  const doSearch = (text: string) => {
    console.log("-------------------->>>>");

    setSearchValue(text);
    let newTree: any = [];
    searchTree(props.tree, text, function(node: any) {
      console.log(node);
      newTree.push(node);
    });

    console.log("===============");
    console.log(newTree); // usunąć item, którego parent jest tablicy
    function hasParentInCollection(item: TreeNode, index, arr) {
      if (arr.find((el: any) => el.name === item.parent)) return false;
      else return true;
    }
    newTree = newTree.filter(hasParentInCollection);
    //set new tree
    console.log("++++++++++++++++++++++++");
    console.log(newTree);
    setTree(newTree);
    // ** Added by Ryan ** Update treeKey state
    setTreeKey(oldKey => oldKey + 1);
  };

  const treeItems = renderTree(tree);
  return (
    <div>
      <div>
        <TreeViewFilter doSearch={doSearch} value={searchValue} />
      </div>
      <div>
        {/* ** key={treeKey} added by Ryan */}
        <TreeView
          key={treeKey}
          className={classes.root}
          defaultCollapseIcon={<ExpandMoreIcon />}
          defaultExpandIcon={<ChevronRightIcon />}
        >
          {treeItems}
        </TreeView>
      </div>
    </div>
  );
}

Edit filtered TreeView