React Tree Multi Select Component for Hierarchical Data

Description:

React Tree Multi Select is a lightweight and customizable React component that combines the functionality of a tree select, multi-select, and a simple select into a single dropdown.

It allows you to render hierarchical data in a tree-like structure and enables your users to select single or multiple items with ease.

Features

  • 🌳 Hierarchical Data Display: Presents nested data in an expandable tree structure.
  • Multi-Select Capability: Users can select multiple nodes simultaneously.
  • 🔍 Search and Filtering: Includes a search functionality to filter tree nodes.
  • 🎨 Customizable: Offers options for custom rendering of nodes and the overall component style.
  • ⌨️ Keyboard Navigation: Supports keyboard interactions for accessibility.
  • 🔄 Controlled and Uncontrolled Modes: Can be used as a controlled or uncontrolled component.

Use Cases

  • File Explorer: For selecting files and folders within a directory structure.
  • Organizational Chart: To select employees or departments from a company hierarchy.
  • Category Selection: In e-commerce or content management systems for assigning items to multiple categories.
  • Permission Management: For assigning user permissions in a system with nested roles.
  • Location Selection: To choose countries, states, and cities from a hierarchical dropdown.

How to Use It

1. Install the package using npm.

npm install react-tree-multi-select

2. Import the component and necessary types in your React file. Use TypeScript for type safety.

import React from 'react';
import { TreeMultiSelect, TreeNode, CheckedState } from 'react-tree-multi-select';

3. Define your hierarchical data following the TreeNode structure. Each node requires a label and can contain children.

const categoryData: TreeNode[] = [
  {
    label: 'Electronics',
    children: [
      { label: 'Laptops' },
      { label: 'Smartphones' }
    ]
  },
  {
    label: 'Clothing',
    children: [
      { label: 'Men' },
      { label: 'Women' }
    ]
  }
];

4. Implement the component. The data prop accepts your tree structure.

function CategorySelector() {
  const [selectedItems, setSelectedItems] = React.useState<TreeNode[]>([]);
  const handleSelectionChange = (node: TreeNode, selectedNodes: TreeNode[]) => {
    setSelectedItems(selectedNodes);
  };
  return (
    <TreeMultiSelect
      data={categoryData}
      onNodeChange={handleSelectionChange}
      withSelectAll={true}
    />
  );
}

5. Configure additional event handlers for expanded state management. The onNodeToggle prop tracks opened nodes.

const handleToggle = (node: TreeNode, expandedNodes: TreeNode[]) => {
  console.log('Expanded nodes:', expandedNodes);
};
<TreeMultiSelect
  data={data}
  onNodeToggle={handleToggle}
  onClearAll={handleClear}
/>

6. All component props.

export interface TreeMultiSelectProps {
  /**
   * The data to be rendered in the component.
   */
  data: TreeNode[];
  /**
   * Specifies the type of the component, determining its behavior and rendering.
   *
   * @default Type.TREE_SELECT
   */
  type?: Type;
  /**
   * The `id` attribute to apply to the root `<div>` of the component.
   */
  id?: string;
  /**
   * The `className` to apply to the root `<div>` of the component.
   */
  className?: string;
  /**
   * Placeholder text displayed in the search input field.
   *
   * @default "search..."
   */
  inputPlaceholder?: string;
  /**
   * Text displayed when there is no data to show in the dropdown.
   *
   * @default "No data"
   */
  noDataText?: string;
  /**
   * Text displayed when no matching results are found during a search.
   *
   * @default "No matches"
   */
  noMatchesText?: string;
  /**
   * Disables the entire component, preventing user interaction.
   *
   * @default false
   */
  isDisabled?: boolean;
  /**
   * Controls whether the search input is rendered.
   * When `true`, a search input is shown either in the field or in the dropdown (if `withDropdownInput` is also `true`).
   *
   * @default true
   */
  isSearchable?: boolean;
  /**
   * Controls whether the chip-level clear button (`ChipClear`) is displayed for each selected item.
   *
   * @default true
   */
  withChipClear?: boolean;
  /**
   * Controls whether the field-level clear button (`FieldClear`) is displayed to clear all selected items at once.
   *
   * @default true
   */
  withClearAll?: boolean;
  /**
   * Controls whether a sticky "SelectAll" component is rendered at the top of the dropdown.
   *
   * This option is automatically hidden when:
   * - `type` is `Type.SELECT`
   * - the search input has a value (search mode)
   * - there is no available data
   *
   * @default false
   */
  withSelectAll?: boolean;
  /**
   * Controls whether a sticky search input is rendered at the top of the dropdown.
   * A hidden input is rendered in the field to preserve focus behavior.
   *
   * @default false
   */
  withDropdownInput?: boolean;
  /**
   * Closes the dropdown automatically after a node is changed (selected/unselected in dropdown).
   * Useful when `type` is `Type.SELECT`.
   *
   * @default false
   */
  closeDropdownOnNodeChange?: boolean;
  /**
   * Controls whether the dropdown is rendered (open) or hidden (closed).
   * This enables external control over the dropdown's rendering state.
   *
   * When set to `true`, the dropdown is rendered (opened).
   * When set to `false`, the dropdown is hidden (closed).
   *
   * If omitted, the component manages the dropdown state internally.
   * For full control, use this prop in conjunction with the `onDropdownToggle` callback.
   */
  openDropdown?: boolean;
  /**
   * Dropdown height in pixels. If the content height is smaller than this value,
   * the dropdown height is automatically reduced to fit the content.
   *
   * @default 300
   */
  dropdownHeight?: number;
  /**
   * The number of items to render outside the visible viewport (above and below)
   * to improve scroll performance and reduce flickering during fast scrolling.
   *
   * @default 1
   */
  overscan?: number;
  /**
   * Determines whether the dropdown list is rendered using virtualization.
   * When enabled, only the visible portion of the list (plus overscan items)
   * is rendered to improve performance with large datasets.
   *
   * @default true
   */
  isVirtualized?: boolean;
  /**
   * Controls when the Footer component is rendered in the dropdown.
   */
  footerConfig?: FooterConfig;
  /**
   * Controls keyboard navigation behavior for the component.
   */
  keyboardConfig?: KeyboardConfig;
  /**
   * Custom components used to override the default UI elements of the TreeMultiSelect.
   *
   * Allows you to replace built-in components with your own implementations to match your design and behavior requirements.
   */
  components?: Components;
  /**
   * Callback triggered when the dropdown is opened or closed by user interaction.
   * This is used to synchronize external state with the dropdown’s rendering state.
   *
   * Note: This callback is only invoked when the `openDropdown` prop is provided.
   * If `openDropdown` is undefined, the component manages its own state and
   * `onDropdownToggle` will not be called.
   *
   * @param open - `true` if the dropdown was opened, `false` if it was closed.
   */
  onDropdownToggle?: (open: boolean) => void;
  /**
   * Callback triggered when a node is selected or unselected.
   * This includes interactions from the dropdown as well as chip removal in the field.
   *
   * @param node - The node that was changed.
   * @param selectedNodes - The list of currently selected nodes.
   * @param data - The full tree data reflecting the updated state.
   */
  onNodeChange?: (node: TreeNode, selectedNodes: TreeNode[], data: TreeNode[]) => void;
  /**
   * Callback triggered when a node is toggled (expanded or collapsed).
   *
   * @param node - The node that was toggled.
   * @param expandedNodes - The list of currently expanded nodes.
   * @param data - The full tree data reflecting the updated state.
   */
  onNodeToggle?: (node: TreeNode, expandedNodes: TreeNode[], data: TreeNode[]) => void;
  /**
   * Callback triggered when the `FieldClear` component is activated by user interaction,
   * such as a mouse click or pressing the Backspace key.
   *
   * This is used to clear all selected nodes, except for nodes that are disabled.
   *
   * @param selectedNodes - The list of currently selected nodes.
   * @param selectAllCheckedState - The current check state of the `SelectAll` component,
   * or `undefined` if the component type is `Type.SELECT`.
   * @param data - The full tree data reflecting the updated state.
   */
  onClearAll?: (selectedNodes: TreeNode[], selectAllCheckedState: CheckedState | undefined, data: TreeNode[]) => void;
  /**
   * Callback triggered when the `SelectAll` component is activated by user interaction,
   * such as a mouse click or pressing the Enter key.
   *
   * This is used to select or unselect all nodes, except for nodes that are disabled.
   *
   * @param selectedNodes - The list of currently selected nodes.
   * @param selectAllCheckedState - The current check state of the `SelectAll` component.
   * @param data - The full tree data reflecting the updated state.
   */
  onSelectAllChange?: (selectedNodes: TreeNode[], selectAllCheckedState: CheckedState, data: TreeNode[]) => void;
  /**
   * Callback triggered when the component receives focus.
   *
   * @param event - The React focus event.
   */
  onFocus?: (event: React.FocusEvent) => void;
  /**
   * Callback triggered when the component loses focus.
   *
   * @param event - The React blur event.
   */
  onBlur?: (event: React.FocusEvent) => void;
  /**
   * Callback triggered when the last item in the dropdown is rendered.
   * This is useful for implementing infinite scrolling or lazy loading.
   *
   * Note: The callback is invoked when the last item (including overscan)
   * is rendered, not based on actual scroll position.
   *
   * @param inputValue - The current search input value.
   * @param displayedNodes - An array of TreeNode objects currently displayed in the dropdown.
   */
  onDropdownLastItemReached?: (inputValue: string, displayedNodes: TreeNode[]) => void;
}

FAQs

Q: Can I customize the appearance of the tree nodes?
A: Yes, the component is highly customizable, allowing you to modify styles and even provide custom rendering for the nodes to fit your application’s design.

Q: Does it support searching or filtering through the tree?
A: Yes, it includes a filtering feature that allows users to quickly search through the tree nodes.

Q: Is it possible to select an entire branch of the tree at once?
A: The component supports multi-level selection, which enables the selection of entire branches with ease.

Q: Can it handle large datasets?
A: The component is designed with performance in mind to efficiently handle large trees without significant performance degradation.

Add Comment