Managing the tree expand/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 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.

Specifying an initial tree expand state
View Mode
Fork
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.
Example of treeExpandState with node paths
const treeExpandState = {
  defaultExpanded: true,
  collapsedPaths: [
    ['1', '10'],
    ['2', '20'],
    ['5']
  ],
  expandedPaths: [
    ['1', '4'],
    ['5','nested node in 5'],
  ],
};
  1. 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.
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}) 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:

Note

The onNodeExpand and 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 or 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 prop.

This will allow you to own the collapse/expand state entirely - but make sure you use the onTreeExpandStateChange callback to react to user actions or API calls being made to update the tree state.

Using controlled expand/collapse state
View Mode
Fork
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);
};

Note

When using controlled treeExpandState, you no longer need to use API calls.

When you need to expand all nodes, simply set the treeExpandState to {defaultExpanded: true, collapsedPaths: []}.

When you need to collapse all nodes, simply set the treeExpandState to {defaultExpanded: false, expandedPaths: []}.