# Using Tree Data

> Learn how to use the Tree DataGrid to display tree data

Canonical page: https://infinite-table.com/docs/learn/tree-grid/overview

Starting with version `6.0.0`, Infinite Table has support for displaying tree data.

To show tree data, you have to use:
 - the `<TreeDataSource />` instead of `<DataSource />` component 
 - the `<TreeGrid />` instead of `<InfiniteTable />` component.

Under the hood, those specialized components have better typing support for tree data, which will make it easier to work with them.

To specify which column will have the expand/collapse icon, set the [`columns.renderTreeIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderTreeIcon) prop to `true` for that column.

**Example: Basic TreeGrid example**

```tsx
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  mimeType?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', renderTreeIcon: true, header: 'Name' },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  mimeType: { field: 'mimeType', header: 'Mime Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

export default function App() {
  return (
    <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
      <TreeGrid columns={columns} />
    </TreeDataSource>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
              mimeType: 'application/pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
          extension: 'txt',
          mimeType: 'text/plain',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '30',
          name: 'Music - empty',
          sizeInKB: 0,
          type: 'folder',
          children: [],
        },
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

Throughout the docs for the TreeGrid, we will use an example data source that illustrates file system data, as that will be familiar to most people.

## Terminology

When referring to rows in the TreeGrid, we'll prefer to use the term `"node"` instead of "row". So whenever you see `"node"` in the docs, you should know that it refers to a TreeGrid configuration of Infinite Table.

Also in the context of the TreeGrid, we'll use the term `"node path"` instead of row id. The `"node path"` is the array with the ids of all the parent nodes leading down to the current node. The node path includes the id of the current node.

```tsx {2} title="Node path vs row id"
const data = [
  { id: '1', name: 'Documents',                // path: ['1']
    children: [
      { id: '10', name: 'Private',             // path: ['1', '10']
        children: [
          { id: '100', name: 'Report.docx' },  // path: ['1', '10', '100'] 
          { id: '101', name: 'Vacation.docx' },// path: ['1', '10', '101']
        ],
      },
    ]
  },
  {
    id: '2',
    name: 'Downloads',                        // path: ['2']
    children: [
      {
        id: '20',
        name: 'cat.jpg',                      // path: ['2', '20']
      },
    ],
  },
];
```
It's important to understand node paths, as that will be the primary way you'll interact with the TreeGrid/TreeDataSource.

For the initial version of the TreeGrid, it's safer if your node ids are unique globally, but as we refine the TreeGrid, it will be safe to use ids unique only within a node children (so unique relative to siblings).

### Parent vs leaf nodes

Nodes with an array for their `nodesKey` property (defaults to `"children"`) are considered parent nodes. All other nodes are leaf nodes.

When using the [`InfiniteTableRowInfo`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) type, you can check for `isTreeNode` to determine if you're in a tree scenario. Also use the `isParentNode` property to check if a node is a parent node or not.

## Data format for the TreeDataSource

When using the `<TreeDataSource />` component, the data you specify in your `[`dataSource`](https://infinite-table.com/docs/reference/datasource-props/index.md#dataSource)` should resolve to a nested array - with the `nodesKey` containing the child items for each tree node.

```tsx {2} title="Using the nodesKey prop to specify where the node children are"
<TreeDataSource
  nodesKey="children"
  primaryKey="id"
  data={dataSource}
/>
```

With the `nodesKey` set to `"children"`, the `<TreeDataSource />` will look for the `children` property on each item in the data array, and use that to determine the child nodes for each tree node. Nodes without a `"children"` property are assumed to be leaf nodes.

```tsx {2} title="Nested data structure for the TreeDataSource component"
const dataSource = [
  {
    id: '1',
    name: 'Documents',
    children: [
      {
        id: '10',
        name: 'Private',
        children: [
          {
            id: '100',
            name: 'Report.docx',
          },
          {
            id: '101',
            name: 'Vacation.docx',
          },
        ],
      },
    ],
  },
  {
    id: '2',
    name: 'Downloads',
    children: [] // will be a parent node, with no children
  },
];
```

## Tree collapse and expand state

The `<TreeDataSource />` component allows you to fully configure & control the collapse and expand state of the tree nodes, via the [`treeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#treeExpandState)/[`defaultTreeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#defaultTreeExpandState) props.

By default, if no expand state is specified, the tree will be rendered as fully expanded.

However, you can choose to specify the expand state with a default value and then with specific values for node paths (or node ids)

**Example: Using controlled tree expand state**

```ts
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeExpandStateValue,
  TreeGrid,
} from '@infinite-table/infinite-react';
import { useState } from 'react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  mimeType?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', header: 'Name', renderTreeIcon: true },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  mimeType: { field: 'mimeType', header: 'Mime Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

export default function App() {
  const [treeExpandState, setTreeExpandState] = useState<TreeExpandStateValue>({
    defaultExpanded: true,
    collapsedPaths: [['1', '10'], ['3']],
  });

  return (
    <>
      <TreeDataSource
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        treeExpandState={treeExpandState}
        onTreeExpandStateChange={setTreeExpandState}
      >
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: '10px',
          }}
        >
          <button
            onClick={() => {
              setTreeExpandState({ defaultExpanded: true, collapsedPaths: [] });
            }}
          >
            Expand all
          </button>
          <button
            onClick={() => {
              setTreeExpandState({
                defaultExpanded: false,
                expandedPaths: [],
              });
            }}
          >
            Collapse all
          </button>
          <div>
            Current tree expand state:
            <pre>{JSON.stringify(treeExpandState, null, 2)}</pre>
          </div>
        </div>

        <TreeGrid columns={columns} />
      </TreeDataSource>
    </>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
              mimeType: 'application/pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '30',
          name: 'Music',
          sizeInKB: 0,
          type: 'folder',
          children: [],
        },
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

When using node paths for [`treeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#treeExpandState), the object should have the following properties:

- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.
- `collapsedPaths`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.
- `expandedPaths`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.

```tsx title="Example of treeExpandState with node paths"
const treeExpandState = {
  defaultExpanded: true,
  collapsedPaths: [
    ['1', '10'],
    ['2', '20'],
    ['5']
  ],
  expandedPaths: [
    ['1', '4'],
    ['5','nested node in 5'],
  ],
};
```

As seen above, you can have a node specifically collapsed while other child nodes specifically expanded.
So you can combine the expanded/collapsed paths to achieve very complex tree layouts, which can be restored later.

## Working with horizontal layout

The [`wrapRowsHorizontally`](https://infinite-table.com/docs/reference/infinite-table-props.md#wrapRowsHorizontally) prop can be used to enable horizontal layout, just like non-tree DataGrids.

**Example: TreeGrid with horizontal layout**

```tsx
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';
import { useState } from 'react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  mimeType?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', renderTreeIcon: true, header: 'Name' },
  type: { field: 'type', header: 'Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size' },
};

export default function App() {
  const [wrapRowsHorizontally, setWrapRowsHorizontally] = useState(false);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <button onClick={() => setWrapRowsHorizontally(!wrapRowsHorizontally)}>
          Toggle horizontal layout
        </button>
      </div>
      <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
        <TreeGrid
          columns={columns}
          wrapRowsHorizontally={wrapRowsHorizontally}
          columnDefaultWidth={100}
        />
      </TreeDataSource>
    </>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
              mimeType: 'application/pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
          extension: 'txt',
          mimeType: 'text/plain',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '30',
          name: 'Music - empty',
          sizeInKB: 0,
          type: 'folder',
          children: [],
        },
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
            {
              id: '311',
              name: 'WinterVacation.mp4',
              sizeInKB: 245,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
            {
              id: '312',
              name: 'SummerVacation.mp4',
              sizeInKB: 1259,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```
