如何有条件地为 material ui TreeView 组件设置扩展属性
How to set expanded prop for material ui TreeView component conditionally
我正在尝试创建一个树 select 组件,就像 antd tree-select using material ui. I have a material-ui TextField and TreeView 组件中的一个组件一样。最初我希望折叠树视图,用户应该能够手动展开它。但是当用户在文本字段中键入一些文本时,我希望展开具有相似文本的节点。我有代码在树中搜索文本并获取匹配节点的节点 ID。有一个名为 expanded
的属性允许我们设置需要扩展的节点 ID 列表。请参阅下面的代码。
import React from 'react';
import PropTypes from 'prop-types';
import { fade, makeStyles } from '@material-ui/core/styles';
import TreeView from '@material-ui/lab/TreeView';
import TreeItem from '@material-ui/lab/TreeItem';
import Typography from '@material-ui/core/Typography';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import ArrowRightIcon from '@material-ui/icons/ArrowRight';
import TextField from '@material-ui/core/TextField';
const useTreeItemStyles = makeStyles(theme => ({
root: {
color: theme.palette.text.secondary,
'&:focus > $content': {
backgroundColor: `var(--tree-view-bg-color, ${theme.palette.grey[400]})`,
color: 'var(--tree-view-color)',
},
},
content: {
color: theme.palette.text.secondary,
paddingRight: theme.spacing(1),
fontWeight: theme.typography.fontWeightMedium,
'$expanded > &': {
fontWeight: theme.typography.fontWeightRegular,
},
},
group: {
marginLeft: 12,
borderLeft: `1px dashed ${fade(theme.palette.text.primary, 0.4)}`,
},
expanded: {},
label: {
fontWeight: 'inherit',
color: 'inherit',
width: 'auto'
},
labelRoot: {
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0.5, 0),
},
labelIcon: {
marginRight: theme.spacing(1),
},
labelText: {
fontWeight: 'inherit',
flexGrow: 1,
},
}));
const useStyles = makeStyles({
root: {
height: 264,
flexGrow: 1,
maxWidth: 400,
},
});
const data = [
{
name: 'world',
id: 'world',
children: [
{
name: 'asia',
id: 'asia',
children: [
{
name: 'india',
id: 'india',
children: [
{
name: 'tamilnadu',
id: 'tamilnadu',
children: [
{
name: 'chennai',
id: 'chennai',
children: [
{
name: 'thiruvanmiyur',
id: 'thiruvanmiyur'
},
{
name: 'kelambakkam',
id: 'kelambakkam'
}
]
},
{
name: 'madurai',
id: 'madurai',
children: [
{
name: 'mattuthavani',
id: 'mattuthavani'
}
]
}
]
},
{
name: 'andhrapradesh',
id: 'andhrapradesh',
children: [
{
name: 'vijayawada',
id: 'vijayawada',
children: [
{
name: 'satyanarayanapuram',
id: 'satyanarayanapuram'
}
]
}
]
},
{
name: 'telangana',
id: 'telangana',
children: [
{
name: 'hyderabad',
id: 'hyderabad',
children: [
{
name: 'dilsukhnagar',
id: 'dilsukhnagar'
}
]
}
]
}
]
},
{
name: 'china',
id: 'china',
children: [
{
name: 'hubei',
id: 'hubei',
children: [
{
name: 'wuhan',
id: 'wuhan'
}
]
}
]
},
{
name: 'japan',
id: 'japan',
children: [
{
name: 'place honshu',
id: 'honshu',
children: [
{
name: 'tokyo',
id: 'tokyo'
}
]
}
]
}
]
},
{
name: 'north america',
id: 'northamerica',
children: [
{
name: 'usa',
id: 'usa',
children: [
{
name: 'place california',
id: 'california',
children: [
{
name: 'losangeles',
id: 'losangeles',
children: [
{
name: 'hollywood',
id: 'hollywood'
}
]
},
{
name: 'sanfrancisco',
id: 'sanfrancisco',
children: [
{
name: 'goldengate',
id: 'goldengate'
}
]
}
]
},
{
name: 'florida',
id: 'florida',
children: [
{
name: 'miami',
id: 'miami',
children: [
{
name: 'place Vizcaya',
id: 'Vizcaya'
}
]
}
]
}
]
}
]
}
]
}
]
function StyledTreeItem(props) {
const { labelText, ...other } = props;
const classes = useTreeItemStyles();
return (
<TreeItem
label={
<div className={classes.labelRoot}>
<Typography variant="body2" className={classes.labelText}>
{labelText}
</Typography>
</div>
}
classes={{
root: classes.root,
content: classes.content,
expanded: classes.expanded,
group: classes.group,
label: classes.label
}}
{...other}
/>
);
}
StyledTreeItem.propTypes = {
bgColor: PropTypes.string,
color: PropTypes.string,
labelIcon: PropTypes.elementType,
labelInfo: PropTypes.string,
labelText: PropTypes.string.isRequired,
};
const filterFunc = (value, searchTerm) => value.toLowerCase().includes(searchTerm);
export default function PlaceTreeView() {
const classes = useStyles();
const [searchTerm, setSearchTerm] = React.useState('');
const [expandNodes, setExpandNodes] = React.useState([]);
const [options, setOptions] = React.useState(data);
const handleSearchTermChange = event => {
setSearchTerm(event.target.value);
searchTree(event.target.value);
}
const getTreeItemsFromData = treeItems => {
return treeItems.map(treeItemData => {
let children = undefined;
if (treeItemData.children && treeItemData.children.length > 0) {
children = getTreeItemsFromData(treeItemData.children);
}
return (
<StyledTreeItem
key={treeItemData.id}
nodeId={treeItemData.id}
labelText={treeItemData.name}
children={children}
highlight={filterFunc(treeItemData.name, searchTerm)}
/>
);
});
};
const searchTree = searchTerm => {
searchTerm = searchTerm.toLowerCase().trim();
if(searchTerm === '') {
return data;
}
let nodesToExpand = [];
function dig(list) {
return list.map(treeNode => {
const { children } = treeNode;
const match = filterFunc(treeNode.name, searchTerm);
const childList = dig(children || [], match);
if(match || childList.length) {
nodesToExpand.push(treeNode.id);
return {
...treeNode,
children: childList
};
}
return null;
})
.filter(node => node);
}
setExpandNodes(nodesToExpand);
setOptions(dig(data));
}
let treeViewProps = {};
if(searchTerm.trim() !== '') {
treeViewProps = { expanded: expandNodes }
}
console.log('treeviewprops', treeViewProps);
return (
<div style={{margin: '20px', display: 'flex', flexDirection: 'column'}}>
<TextField style={{width: '200px', marginBottom: '10px'}} id="standard-basic" label="Search place" onChange={handleSearchTermChange} />
<TreeView
className={classes.root}
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
// expanded={expandNodes}
{...treeViewProps}
defaultEndIcon={<div style={{ width: 24 }} />}
>
{getTreeItemsFromData(options)}
</TreeView>
</div>
);
}
如果我控制 TreeView
组件并将 expanded=[]
设置为初始状态。然后它不让用户在开始时文本字段中没有文本时手动展开。如果我将 expanded=[list of all nodes in tree]
设置为初始状态,则它会显示默认展开的所有节点。但我不想那样。我希望它最初折叠到根目录,然后让用户手动展开节点。所以我试图让 expanded
道具成为条件。但后来我得到这个错误
Material-UI: A component is changing an uncontrolled TreeView to be controlled. Elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled TreeView element for the lifetime of the component.
如何避免此错误并有条件地设置道具?或者有没有其他方法可以达到我想要达到的目的?
可能是你控制的时候没有设置回调。 (与 TextField 的 value
和 onChange
相同)
TreeView API 文档 here
你可以在那里找到回调 onNodeToggle
设置它会解决这个问题。
我正在尝试创建一个树 select 组件,就像 antd tree-select using material ui. I have a material-ui TextField and TreeView 组件中的一个组件一样。最初我希望折叠树视图,用户应该能够手动展开它。但是当用户在文本字段中键入一些文本时,我希望展开具有相似文本的节点。我有代码在树中搜索文本并获取匹配节点的节点 ID。有一个名为 expanded
的属性允许我们设置需要扩展的节点 ID 列表。请参阅下面的代码。
import React from 'react';
import PropTypes from 'prop-types';
import { fade, makeStyles } from '@material-ui/core/styles';
import TreeView from '@material-ui/lab/TreeView';
import TreeItem from '@material-ui/lab/TreeItem';
import Typography from '@material-ui/core/Typography';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import ArrowRightIcon from '@material-ui/icons/ArrowRight';
import TextField from '@material-ui/core/TextField';
const useTreeItemStyles = makeStyles(theme => ({
root: {
color: theme.palette.text.secondary,
'&:focus > $content': {
backgroundColor: `var(--tree-view-bg-color, ${theme.palette.grey[400]})`,
color: 'var(--tree-view-color)',
},
},
content: {
color: theme.palette.text.secondary,
paddingRight: theme.spacing(1),
fontWeight: theme.typography.fontWeightMedium,
'$expanded > &': {
fontWeight: theme.typography.fontWeightRegular,
},
},
group: {
marginLeft: 12,
borderLeft: `1px dashed ${fade(theme.palette.text.primary, 0.4)}`,
},
expanded: {},
label: {
fontWeight: 'inherit',
color: 'inherit',
width: 'auto'
},
labelRoot: {
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0.5, 0),
},
labelIcon: {
marginRight: theme.spacing(1),
},
labelText: {
fontWeight: 'inherit',
flexGrow: 1,
},
}));
const useStyles = makeStyles({
root: {
height: 264,
flexGrow: 1,
maxWidth: 400,
},
});
const data = [
{
name: 'world',
id: 'world',
children: [
{
name: 'asia',
id: 'asia',
children: [
{
name: 'india',
id: 'india',
children: [
{
name: 'tamilnadu',
id: 'tamilnadu',
children: [
{
name: 'chennai',
id: 'chennai',
children: [
{
name: 'thiruvanmiyur',
id: 'thiruvanmiyur'
},
{
name: 'kelambakkam',
id: 'kelambakkam'
}
]
},
{
name: 'madurai',
id: 'madurai',
children: [
{
name: 'mattuthavani',
id: 'mattuthavani'
}
]
}
]
},
{
name: 'andhrapradesh',
id: 'andhrapradesh',
children: [
{
name: 'vijayawada',
id: 'vijayawada',
children: [
{
name: 'satyanarayanapuram',
id: 'satyanarayanapuram'
}
]
}
]
},
{
name: 'telangana',
id: 'telangana',
children: [
{
name: 'hyderabad',
id: 'hyderabad',
children: [
{
name: 'dilsukhnagar',
id: 'dilsukhnagar'
}
]
}
]
}
]
},
{
name: 'china',
id: 'china',
children: [
{
name: 'hubei',
id: 'hubei',
children: [
{
name: 'wuhan',
id: 'wuhan'
}
]
}
]
},
{
name: 'japan',
id: 'japan',
children: [
{
name: 'place honshu',
id: 'honshu',
children: [
{
name: 'tokyo',
id: 'tokyo'
}
]
}
]
}
]
},
{
name: 'north america',
id: 'northamerica',
children: [
{
name: 'usa',
id: 'usa',
children: [
{
name: 'place california',
id: 'california',
children: [
{
name: 'losangeles',
id: 'losangeles',
children: [
{
name: 'hollywood',
id: 'hollywood'
}
]
},
{
name: 'sanfrancisco',
id: 'sanfrancisco',
children: [
{
name: 'goldengate',
id: 'goldengate'
}
]
}
]
},
{
name: 'florida',
id: 'florida',
children: [
{
name: 'miami',
id: 'miami',
children: [
{
name: 'place Vizcaya',
id: 'Vizcaya'
}
]
}
]
}
]
}
]
}
]
}
]
function StyledTreeItem(props) {
const { labelText, ...other } = props;
const classes = useTreeItemStyles();
return (
<TreeItem
label={
<div className={classes.labelRoot}>
<Typography variant="body2" className={classes.labelText}>
{labelText}
</Typography>
</div>
}
classes={{
root: classes.root,
content: classes.content,
expanded: classes.expanded,
group: classes.group,
label: classes.label
}}
{...other}
/>
);
}
StyledTreeItem.propTypes = {
bgColor: PropTypes.string,
color: PropTypes.string,
labelIcon: PropTypes.elementType,
labelInfo: PropTypes.string,
labelText: PropTypes.string.isRequired,
};
const filterFunc = (value, searchTerm) => value.toLowerCase().includes(searchTerm);
export default function PlaceTreeView() {
const classes = useStyles();
const [searchTerm, setSearchTerm] = React.useState('');
const [expandNodes, setExpandNodes] = React.useState([]);
const [options, setOptions] = React.useState(data);
const handleSearchTermChange = event => {
setSearchTerm(event.target.value);
searchTree(event.target.value);
}
const getTreeItemsFromData = treeItems => {
return treeItems.map(treeItemData => {
let children = undefined;
if (treeItemData.children && treeItemData.children.length > 0) {
children = getTreeItemsFromData(treeItemData.children);
}
return (
<StyledTreeItem
key={treeItemData.id}
nodeId={treeItemData.id}
labelText={treeItemData.name}
children={children}
highlight={filterFunc(treeItemData.name, searchTerm)}
/>
);
});
};
const searchTree = searchTerm => {
searchTerm = searchTerm.toLowerCase().trim();
if(searchTerm === '') {
return data;
}
let nodesToExpand = [];
function dig(list) {
return list.map(treeNode => {
const { children } = treeNode;
const match = filterFunc(treeNode.name, searchTerm);
const childList = dig(children || [], match);
if(match || childList.length) {
nodesToExpand.push(treeNode.id);
return {
...treeNode,
children: childList
};
}
return null;
})
.filter(node => node);
}
setExpandNodes(nodesToExpand);
setOptions(dig(data));
}
let treeViewProps = {};
if(searchTerm.trim() !== '') {
treeViewProps = { expanded: expandNodes }
}
console.log('treeviewprops', treeViewProps);
return (
<div style={{margin: '20px', display: 'flex', flexDirection: 'column'}}>
<TextField style={{width: '200px', marginBottom: '10px'}} id="standard-basic" label="Search place" onChange={handleSearchTermChange} />
<TreeView
className={classes.root}
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
// expanded={expandNodes}
{...treeViewProps}
defaultEndIcon={<div style={{ width: 24 }} />}
>
{getTreeItemsFromData(options)}
</TreeView>
</div>
);
}
如果我控制 TreeView
组件并将 expanded=[]
设置为初始状态。然后它不让用户在开始时文本字段中没有文本时手动展开。如果我将 expanded=[list of all nodes in tree]
设置为初始状态,则它会显示默认展开的所有节点。但我不想那样。我希望它最初折叠到根目录,然后让用户手动展开节点。所以我试图让 expanded
道具成为条件。但后来我得到这个错误
Material-UI: A component is changing an uncontrolled TreeView to be controlled. Elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled TreeView element for the lifetime of the component.
如何避免此错误并有条件地设置道具?或者有没有其他方法可以达到我想要达到的目的?
可能是你控制的时候没有设置回调。 (与 TextField 的 value
和 onChange
相同)
TreeView API 文档 here
你可以在那里找到回调 onNodeToggle
设置它会解决这个问题。