Examples
Search & Filter
Tree view with search functionality
This example shows how to implement search/filter functionality.
- Fruits
- Vegetables
Code
import * as React from "react";
import { TreeView, TreeViewItem, flattenTree } from "shadcn-treeview";
import { FileIcon, FolderIcon, FolderOpenIcon, SearchIcon } from "lucide-react";
type TreeNode = {
id: string;
name: string;
children?: TreeNode[];
parent?: string;
isBranch?: boolean;
};
export function SearchTreeView() {
const treeData: TreeNode = React.useMemo(
() => ({
id: "root",
name: "Root",
children: [
{
id: "1",
parent: "root",
name: "Fruits",
isBranch: true,
children: [
{ id: "11", name: "Apple", parent: "1" },
{ id: "12", name: "Banana", parent: "1" },
{ id: "13", name: "Cherry", parent: "1" }
]
},
{
id: "2",
parent: "root",
name: "Vegetables",
isBranch: true,
children: [
{ id: "21", name: "Carrot", parent: "2" },
{ id: "22", name: "Broccoli", parent: "2" },
{ id: "23", name: "Spinach", parent: "2" }
]
}
]
}),
[]
);
const flatData = React.useMemo(() => flattenTree(treeData), [treeData]);
const [searchTerm, setSearchTerm] = React.useState("");
const [filteredData, setFilteredData] = React.useState(treeData);
const filterTree = React.useCallback(
(searchValue: string): TreeNode => {
const upperValue = searchValue.toUpperCase();
const filteredNodes: Set<string> = new Set();
// Include parents of matching nodes
const includeParents = (nodeId: string) => {
const node = flatData.find((item) => item.id === nodeId);
if (node && !filteredNodes.has(node.id.toString())) {
filteredNodes.add(node.id.toString());
if (node.parent) {
includeParents(node.parent.toString());
}
}
};
// Find matching nodes
flatData.forEach((item) => {
if (item.name.toUpperCase().includes(upperValue)) {
includeParents(item.id.toString());
}
});
// Build filtered tree
const buildFilteredTree = (nodeId: string): TreeNode | undefined => {
const node = flatData.find((item) => item.id === nodeId);
if (!node || !filteredNodes.has(node.id.toString())) return undefined;
return {
id: node.id.toString(),
parent: node.parent?.toString(),
name: node.name,
isBranch: node.isBranch,
children: node.children
?.map((child) => buildFilteredTree(child.toString()))
.filter(Boolean) as TreeNode[]
};
};
return buildFilteredTree("root")!;
},
[flatData]
);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newSearchTerm = e.target.value;
setSearchTerm(newSearchTerm);
setFilteredData(newSearchTerm ? filterTree(newSearchTerm) : treeData);
};
return (
<div className="w-64 space-y-2">
<div className="relative">
<SearchIcon className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={handleSearchChange}
className="w-full rounded border bg-background py-2 pl-8 pr-3 text-sm"
/>
</div>
<TreeView
data={flattenTree(filteredData)}
aria-label="Searchable tree"
className="rounded border p-2"
nodeRenderer={({
element,
isBranch,
isExpanded,
isSelected,
getNodeProps,
level
}) => (
<TreeViewItem
{...getNodeProps()}
name={element.name}
isBranch={isBranch}
isExpanded={isExpanded}
isSelected={isSelected}
level={level}
indentation={16}
icon={
isBranch ? (
isExpanded ? (
<FolderOpenIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
) : (
<FolderIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
)
) : (
<FileIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
)
}
/>
)}
/>
</div>
);
}