Managing the tree column and expand/collapse icon

When rendering a tree, you have to use the <TreeGrid /> component instead of <InfiniteTable />.

The <TreeGrid /> component is simply an <InfiniteTable /> component with some props removed - those don't make sense for tree scenarios.

Note

By default no tree column is rendered.

To specify the tree column, you have to to set the columns.renderTreeIcon prop to true for your column of choice.

Specifying the tree column
const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { 
    renderTreeIcon: true,
    field: 'name',
    header: 'Name' 
  },
  type: { field: 'type', header: 'Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

This is very similar to how you specify the selection column for multi-select configurations.

Specifying the tree column
View Mode
Fork
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';
import { useMemo, useState } from 'react';

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

const allColumns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', 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() {
  const [treeIcon, setTreeIcon] = useState<string>('name');

  const columns = useMemo(() => {
    const cols = { ...allColumns };

    cols[treeIcon] = {
      ...cols[treeIcon],
      renderTreeIcon: true,
    };

    return cols;
  }, [treeIcon]);
  return (
    <>
      <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: '10px',
          }}
        >
          <p>Select the tree column</p>
          <select
            value={treeIcon}
            onChange={(e) => setTreeIcon(e.target.value)}
            title="Select column to use for the tree icon"
          >
            {Object.keys(allColumns).map((key) => (
              <option value={key}>{key}</option>
            ))}
            <option value="none">No tree column</option>
          </select>
        </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: '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);
};

Customizing the expand/collapse icon

Using the column.renderTreeIcon=true is obviously not enough to customize the expand/collapse icon.

This prop can also be a function that returns a React node.

Note

With the default value of true for columns.renderTreeIcon, an icon will be rendered only for parent nodes.

If you want to render an icon for all nodes, specify a function (and differentiate between parent and leaf nodes), and it will be called regardless of whether the node is a parent or a leaf.

Customizing the expand/collapse icon
View Mode
Fork
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';
import { useMemo, useState } from 'react';

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

const allColumns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', 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() {
  const [treeIcon, setTreeIcon] = useState<string>('name');

  const columns = useMemo(() => {
    const cols = { ...allColumns };

    cols[treeIcon] = {
      ...cols[treeIcon],
      renderTreeIcon: ({ rowInfo, toggleCurrentTreeNode }) => (
        <div
          onClick={toggleCurrentTreeNode}
          style={{
            width: 24,
            fontSize: 12,
            display: 'inline-block',
            cursor: 'pointer',
          }}
        >
          {rowInfo.isParentNode ? (rowInfo.nodeExpanded ? '👇' : '👉') : '🔴'}
        </div>
      ),
    };

    return cols;
  }, [treeIcon]);
  return (
    <>
      <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: '10px',
          }}
        >
          <p>Select the tree column</p>
          <select
            value={treeIcon}
            onChange={(e) => setTreeIcon(e.target.value)}
            title="Select column to use for the tree icon"
          >
            {Object.keys(allColumns).map((key) => (
              <option value={key}>{key}</option>
            ))}
            <option value="no">No tree column</option>
          </select>
        </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: 108,
          type: 'file',
          extension: 'txt',
          mimeType: 'text/plain',
        },
      ],
    },
    {
      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);
};