{"slug":"collection","title":"Collection","description":"Working with collections in Zag","contentType":"guides","content":"The Collection class is designed to manage a collection of items, providing\nfunctionalities such as sorting, searching, getting next or previous items,\nconverting items to values or strings, checking if an item is disabled, and\nmore.\n\n> **Good to know**: This is used in the select and combobox components\n\n## List Collection\n\nA list collection is a collection that is based on an array of items. It is\ncreated by passing an array of items to the constructor.\n\n```ts\nimport { ListCollection } from \"@zag-js/collection\"\n\nconst collection = new ListCollection({\n  items: [\n    { label: \"Apple\", value: \"apple\" },\n    { label: \"Banana\", value: \"banana\" },\n  ],\n})\n```\n\n### Converting value to item\n\nYou can convert a value to an item by passing the value to the `find` or\n`findMany` method.\n\n```ts\nconst item = collection.find(\"banana\")\n\nconsole.log(item) // { label: \"Banana\", value: \"banana\" }\n\nconst items = collection.findMany([\"apple\", \"banana\"])\n\nconsole.log(items) // [{ label: \"Apple\", value: \"apple\" }, { label: \"Banana\", value: \"banana\" }]\n```\n\n### Value Traversal\n\nYou can get the next or previous item based on a value by passing the value to\nthe `getNextValue` or `getPreviousValue` method.\n\n```ts\nconst nextValue = collection.getNextValue(\"apple\")\n\nconsole.log(nextValue) // banana\n\nconst previousItem = collection.getPreviousValue(\"banana\")\n\nconsole.log(previousItem) // apple\n```\n\nLikewise, you can also get the first or last item by calling the `firstValue` or\n`lastValue` computed properties.\n\n```ts\nconsole.log(collection.firstValue) // apple\n\nconsole.log(collection.lastValue) // banana\n```\n\n### Check for value existence\n\nYou can check if a value exists in the collection by calling the `has` method.\n\n```ts\nconst hasValue = collection.has(\"apple\")\n\nconsole.log(hasValue) // true\n```\n\n### Working with custom objects\n\nIf you are working with custom objects, you can pass a function to the\n`itemToString` and `getItemValue` options to specify how to convert an item to a\nstring and a value, respectively.\n\n```ts\nimport { ListCollection } from \"@zag-js/collection\"\n\nconst collection = new ListCollection({\n  items: [\n    { id: 1, name: \"apple\" },\n    { id: 2, name: \"banana\" },\n    { id: 3, name: \"cherry\" },\n  ],\n  itemToString: (item) => item.name,\n  itemToValue: (item) => item.id,\n})\n```\n\nTo disable an item, you can pass a function to the `isItemDisabled` option.\n\n```ts\nimport { ListCollection } from \"@zag-js/collection\"\n\nconst collection = new ListCollection({\n  items: [\n    { id: 1, name: \"apple\" },\n    { id: 2, name: \"banana\" },\n    { id: 3, name: \"cherry\" },\n  ],\n  isItemDisabled: (item) => item.id === 2,\n})\n```\n\n### Reorder items\n\nYou can reorder items by calling the `reorder` method and passing the starting\nindex and the ending index of the item to be moved.\n\n```ts\nconst fromIndex = 1 // Banana\nconst toIndex = 0 // Apple\ncollection.reorder(fromIndex, toIndex)\n\nconsole.log(collection.items) // [{ label: \"Banana\", value: \"banana\" }, { label: \"Apple\", value: \"apple\" }]\n```\n\n## Tree Collection\n\nA tree collection is designed to manage hierarchical data structures like file\nsystems, navigation menus, or organization charts. It provides powerful methods\nfor traversing, manipulating, and querying tree structures.\n\n```ts\nimport { TreeCollection } from \"@zag-js/collection\"\n\nconst treeData = {\n  value: \"root\",\n  label: \"Root\",\n  children: [\n    {\n      value: \"folder1\",\n      label: \"Folder 1\",\n      children: [\n        { value: \"file1\", label: \"File 1.txt\" },\n        { value: \"file2\", label: \"File 2.txt\" },\n      ],\n    },\n    {\n      value: \"folder2\",\n      label: \"Folder 2\",\n      children: [\n        {\n          value: \"subfolder1\",\n          label: \"Subfolder 1\",\n          children: [{ value: \"file3\", label: \"File 3.txt\" }],\n        },\n      ],\n    },\n  ],\n}\n\nconst tree = new TreeCollection({ rootNode: treeData })\n```\n\n### Navigation Methods\n\nThe tree collection provides various methods to navigate through the\nhierarchical structure.\n\n#### Getting First and Last Nodes\n\n```ts\nconst firstNode = tree.getFirstNode()\nconsole.log(firstNode?.value) // \"folder1\"\n\nconst lastNode = tree.getLastNode()\nconsole.log(lastNode?.value) // \"folder2\"\n```\n\n#### Sequential Navigation\n\nNavigate to the next or previous node in the tree traversal order:\n\n```ts\nconst nextNode = tree.getNextNode(\"file1\")\nconsole.log(nextNode?.value) // \"file2\"\n\nconst previousNode = tree.getPreviousNode(\"file2\")\nconsole.log(previousNode?.value) // \"file1\"\n```\n\n### Hierarchical Relationships\n\n#### Parent and Children\n\nGet parent and descendant nodes:\n\n```ts\n// Get parent node\nconst parentNode = tree.getParentNode(\"file1\")\nconsole.log(parentNode?.value) // \"folder1\"\n\n// Get all ancestor nodes\nconst ancestors = tree.getParentNodes(\"file3\")\nconsole.log(ancestors.map((n) => n.value)) // [\"folder2\", \"subfolder1\"]\n\n// Get all descendant nodes\nconst descendants = tree.getDescendantNodes(\"folder1\")\nconsole.log(descendants.map((n) => n.value)) // [\"file1\", \"file2\"]\n\n// Get descendant values only\nconst descendantValues = tree.getDescendantValues(\"folder2\")\nconsole.log(descendantValues) // [\"subfolder1\", \"file3\"]\n```\n\n#### Sibling Navigation\n\nNavigate between sibling nodes:\n\n```ts\n// Assuming we have the index path of \"file1\"\nconst indexPath = tree.getIndexPath(\"file1\") // [0, 0]\n\nconst nextSibling = tree.getNextSibling(indexPath)\nconsole.log(nextSibling?.value) // \"file2\"\n\nconst previousSibling = tree.getPreviousSibling(indexPath)\nconsole.log(previousSibling) // undefined (no previous sibling)\n\n// Get all siblings\nconst siblings = tree.getSiblingNodes(indexPath)\nconsole.log(siblings.map((n) => n.value)) // [\"file1\", \"file2\"]\n```\n\n### Index Path Operations\n\nWork with index paths to identify node positions:\n\n```ts\n// Get index path for a value\nconst indexPath = tree.getIndexPath(\"file3\")\nconsole.log(indexPath) // [1, 0, 0]\n\n// Get value from index path\nconst value = tree.getValue([1, 0, 0])\nconsole.log(value) // \"file3\"\n\n// Get full value path (all ancestors + node)\nconst valuePath = tree.getValuePath([1, 0, 0])\nconsole.log(valuePath) // [\"folder2\", \"subfolder1\", \"file3\"]\n\n// Get node at specific index path\nconst node = tree.at([1, 0])\nconsole.log(node?.value) // \"subfolder1\"\n```\n\n### Tree Queries\n\n#### Branch and Leaf Detection\n\n```ts\n// Check if a node is a branch (has children)\nconst folder1Node = tree.findNode(\"folder1\")\nconst isBranch = tree.isBranchNode(folder1Node!)\nconsole.log(isBranch) // true\n\n// Get all branch values\nconst branchValues = tree.getBranchValues()\nconsole.log(branchValues) // [\"folder1\", \"folder2\", \"subfolder1\"]\n```\n\n#### Tree Traversal\n\nVisit all nodes with custom logic:\n\n```ts\ntree.visit({\n  onEnter: (node, indexPath) => {\n    console.log(`Visiting: ${node.value} at depth ${indexPath.length}`)\n\n    // Skip certain branches\n    if (node.value === \"folder2\") {\n      return \"skip\" // Skip this branch and its children\n    }\n  },\n})\n```\n\n#### Filtering\n\nCreate a new tree with filtered nodes:\n\n```ts\n// Keep only nodes that match criteria\nconst filteredTree = tree.filter((node, indexPath) => {\n  return node.value.includes(\"file\") // Only keep files\n})\n\nconsole.log(filteredTree.getValues()) // [\"file1\", \"file2\", \"file3\"]\n```\n\n### Tree Manipulation\n\n#### Adding Nodes\n\n```ts\nconst newFile = { value: \"newfile\", label: \"New File.txt\" }\n\n// Insert after a specific node\nconst indexPath = tree.getIndexPath(\"file1\")\nconst updatedTree = tree.insertAfter(indexPath!, [newFile])\n\n// Insert before a specific node\nconst updatedTree2 = tree.insertBefore(indexPath!, [newFile])\n```\n\n#### Removing Nodes\n\n```ts\nconst indexPath = tree.getIndexPath(\"file2\")\nconst updatedTree = tree.remove([indexPath!])\n\nconsole.log(updatedTree.getValues()) // file2 is removed\n```\n\n#### Moving Nodes\n\n```ts\nconst fromIndexPaths = [tree.getIndexPath(\"file1\")!]\nconst toIndexPath = tree.getIndexPath(\"folder2\")!\n\nconst updatedTree = tree.move(fromIndexPaths, toIndexPath)\n// file1 is now moved under folder2\n```\n\n#### Replacing Nodes\n\n```ts\nconst indexPath = tree.getIndexPath(\"file1\")!\nconst newNode = { value: \"replacedfile\", label: \"Replaced File.txt\" }\n\nconst updatedTree = tree.replace(indexPath, newNode)\n```\n\n### Utility Methods\n\n#### Flattening\n\nConvert the tree to a flat structure:\n\n```ts\nconst flatNodes = tree.flatten()\nconsole.log(\n  flatNodes.map((n) => ({ value: n.value, depth: n._indexPath.length })),\n)\n// [{ value: \"folder1\", depth: 1 }, { value: \"file1\", depth: 2 }, ...]\n```\n\n#### Getting All Values\n\n```ts\nconst allValues = tree.getValues()\nconsole.log(allValues) // [\"folder1\", \"file1\", \"file2\", \"folder2\", \"subfolder1\", \"file3\"]\n```\n\n#### Depth Calculation\n\n```ts\nconst depth = tree.getDepth(\"file3\")\nconsole.log(depth) // 3 (root -> folder2 -> subfolder1 -> file3)\n```\n\n### Working with Custom Node Types\n\nYou can customize how the tree collection interprets your data:\n\n```ts\ninterface CustomNode {\n  id: string\n  name: string\n  items?: CustomNode[]\n  isDisabled?: boolean\n}\n\nconst customTree = new TreeCollection<CustomNode>({\n  rootNode: {\n    id: \"root\",\n    name: \"Root\",\n    items: [\n      { id: \"1\", name: \"Item 1\", isDisabled: false },\n      { id: \"2\", name: \"Item 2\", isDisabled: true },\n    ],\n  },\n  nodeToValue: (node) => node.id,\n  nodeToString: (node) => node.name,\n  nodeToChildren: (node) => node.items,\n  isNodeDisabled: (node) => node.isDisabled ?? false,\n})\n```\n\n### Creating Trees from File Paths\n\nCreate a tree structure from file paths:\n\n```ts\nimport { filePathToTree } from \"@zag-js/collection\"\n\nconst paths = [\n  \"src/components/Button.tsx\",\n  \"src/components/Input.tsx\",\n  \"src/utils/helpers.ts\",\n  \"docs/README.md\",\n]\n\nconst fileTree = filePathToTree(paths)\nconsole.log(fileTree.getBranchValues()) // [\"src\", \"components\", \"utils\", \"docs\"]\n```\n\n> **Good to know**: Tree collections are immutable - all modification methods\n> return a new tree instance rather than modifying the original.","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/guides/collection.mdx"}