# Managing the tree expand/collapse state

> Learn how to control which tree nodes are expanded or collapsed

Canonical page: https://infinite-table.com/docs/learn/tree-grid/tree-expand-and-collapse-state

By default, the tree will be rendered with all nodes expanded. This is fine for basic use cases, but as soon as you go into more complex scenarios, you will want to control which nodes are expanded or collapsed.

This is easy to achieve via the [`defaultTreeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#defaultTreeExpandState) prop. This is an uncontrolled prop and allows you to initially specify the expand/collapse state of the tree - all subsequent user updates will result in the tree state being updated to match the UI actions.

**Example: Specifying an initial tree expand state**

```tsx
import {
  DataSourceApi,
  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)' },
};

const defaultTreeExpandState: TreeExpandStateValue = {
  defaultExpanded: true,
  collapsedPaths: [
    ['1', '10'],
    ['3', '31'],
  ],
  expandedPaths: [['3']],
};

export default function App() {
  const [dataSourceApi, setDataSourceApi] =
    useState<DataSourceApi<FileSystemNode> | null>();

  return (
    <>
      <TreeDataSource
        onReady={setDataSourceApi}
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        defaultTreeExpandState={defaultTreeExpandState}
      >
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: '10px',
          }}
        >
          <button
            onClick={() => {
              dataSourceApi!.treeApi.expandAll();
            }}
          >
            Expand all
          </button>
          <button
            onClick={() => {
              dataSourceApi!.treeApi.collapseAll();
            }}
          >
            Collapse all
          </button>
        </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 - 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);
};
```

## Understanding the tree expand state

You can specify the expand/collapse state of the tree in two ways:

1. With node paths (recommended)

When using node paths, 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'],
  ],
};
```

2. With node ids

When using node ids, the object should have the following properties:

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

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

## Reacting to user actions

You can listen to the user interactions with the tree by using the [{`onTreeExpandStateChange(treeExpandState, {dataSourceApi, nodePath, nodeState})`}](https://infinite-table.com/docs/reference/datasource-props/index.md#onTreeExpandStateChange) callback. This callback is called with the new tree state whenever the user expands or collapses a node.

In addition to this callback, you can also use the following:

 - [{`onNodeExpand(nodePath, {dataSourceApi})`}](https://infinite-table.com/docs/reference/datasource-props/index.md#onNodeExpand)
 - [{`onNodeCollapse(nodePath, {dataSourceApi})`}](https://infinite-table.com/docs/reference/datasource-props/index.md#onNodeCollapse)

The [`onNodeExpand`](https://infinite-table.com/docs/reference/datasource-props/index.md#onNodeExpand) and [`onNodeCollapse`](https://infinite-table.com/docs/reference/datasource-props/index.md#onNodeCollapse) callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the [`expandAll`](https://infinite-table.com/docs/reference/tree-api/index.md#expandAll) or [`collapseAll`](https://infinite-table.com/docs/reference/tree-api/index.md#collapseAll) methods are called.

## Using controlled expand/collapse state

If you want maximum control over the collapse/expand state, you should use the controlled [`treeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#treeExpandState) prop.

This will allow you to own the collapse/expand state entirely - but make sure you use the [`onTreeExpandStateChange`](https://infinite-table.com/docs/reference/datasource-props/index.md#onTreeExpandStateChange) callback to react to user actions or API calls being made to update the tree state.

**Example: Using controlled expand/collapse state**

```tsx
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 controlled [`treeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#treeExpandState), you no longer need to use API calls.

When you need to expand all nodes, simply set the [`treeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#treeExpandState) to `{defaultExpanded: true, collapsedPaths: []}`.

When you need to collapse all nodes, simply set the [`treeExpandState`](https://infinite-table.com/docs/reference/datasource-props/index.md#treeExpandState) to `{defaultExpanded: false, expandedPaths: []}`.
