我对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)
我想知道这是用户界面错误还是我做错了什么?
答案 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>
);
}