async-tree-select.tsx
Overview
async-tree-select.tsx defines a React functional component AsyncTreeSelect that provides a hierarchical tree-based selection UI with asynchronous loading capabilities. It is designed for use cases where tree nodes may be dynamically loaded on demand (e.g., lazy loading child nodes from a server). The component displays a dropdown popover containing a collapsible tree structure, allowing users to select a node. Nodes can be expanded or collapsed, and if children are not yet loaded, the component triggers asynchronous loading via a user-supplied callback.
Key features include:
Render hierarchical tree data with expandable/collapsible nodes.
Asynchronous loading of child nodes when expanding a parent with no loaded children.
Single selection mode with a controlled
valueprop.Keyboard and mouse interaction via buttons and list items.
Integration with i18n translation for placeholder text.
Visual loading indicator for nodes asynchronously fetching data.
Component and Types
Types
type TreeId = number | string;
Alias for node identifiers, supporting either numbers or strings.
export type TreeNodeType = {
id: TreeId;
title: ReactNode; // Node display content
parentId: TreeId; // Parent node ID
isLeaf?: boolean; // Optional flag indicating leaf node (no children)
};
Represents a single tree node with ID, display title, parent reference, and optional leaf status.
type AsyncTreeSelectProps = {
treeData: TreeNodeType[]; // Flat array of all tree nodes
value?: TreeId; // Currently selected node id (controlled)
onChange?(value: TreeId): void; // Callback when selection changes
loadData?(node: TreeNodeType): Promise<any>; // Async loader for node's children
};
Props accepted by the
AsyncTreeSelectcomponent.
AsyncTreeSelect Component
export function AsyncTreeSelect({
treeData,
value,
loadData,
onChange,
}: AsyncTreeSelectProps) { ... }
Description
Renders an interactive tree-select dropdown with async loading support. Presents selected node title or a placeholder when none selected. Displays nodes in a nested list where parents can be expanded/collapsed by arrow buttons. Loads data asynchronously for nodes lacking children when expanded.
Parameters
treeData: TreeNodeType[]: Flat array representing the entire tree structure.value?: TreeId: The currently selected node's ID.loadData?(node: TreeNodeType): Promise<any>: Optional async function to load children of a given node.onChange?(value: TreeId): void: Callback invoked when user selects a different node.
Returns
JSX element rendering the tree-select UI.
Detailed Implementation
State Variables
open: boolean— Controls whether the dropdown popover is open.expandedKeys: TreeId[]— IDs of nodes currently expanded.loadingId: TreeId— ID of the node currently loading children (shows spinner).selectedTitle: ReactNode— Memoized title of selected node for display.
Hooks and Callbacks
isExpanded(id: TreeId | undefined): boolean
Checks if a node ID is in the expanded keys list. Root level (undefined) is always expanded.handleNodeClick(id: TreeId): (e) => void
Handles selection of a node. Stops event propagation, triggersonChangewith selected ID, and closes dropdown.handleArrowClick(node: TreeNodeType): (e) => Promise<void>
Handles expand/collapse toggle for a node arrow button.If expanded, collapses by removing ID from
expandedKeys.If collapsed, expands by adding ID.
If node has no children yet, calls
loadDatacallback and shows loading spinner.
renderNodes(parentId?: TreeId)
Recursively renders a nested<ul>list of nodes under the givenparentId.Finds immediate children of the parent.
For each node, renders title, selection highlight, and expand/collapse button if not leaf.
Recurses for each expanded node's children.
Effects
On mount or when
treeDatais empty, callsloadDatawith an empty root node to initialize data if loader provided.
Render Output
Uses
Popovercomponent to render dropdown.PopoverTriggeris a div showing selected node title or placeholder with a down arrow.PopoverContentcontains the rendered nested tree nodes.
Usage Example
import { AsyncTreeSelect, TreeNodeType } from './async-tree-select';
const initialData: TreeNodeType[] = [
{ id: '1', title: 'Root', parentId: '', isLeaf: false },
];
async function fetchChildren(node: TreeNodeType) {
// Fetch children from API or other source
const children = await api.getChildren(node.id);
// Update tree data state externally
}
function MyComponent() {
const [treeData, setTreeData] = useState<TreeNodeType[]>(initialData);
const [selected, setSelected] = useState<TreeId>();
return (
<AsyncTreeSelect
treeData={treeData}
value={selected}
onChange={setSelected}
loadData={async (node) => {
const children = await fetchChildren(node);
setTreeData([...treeData, ...children]);
}}
/>
);
}
Important Implementation Details
Flat Tree Data Structure: The component expects a flat array of nodes with
parentIdreferences rather than a nested object. This simplifies filtering and rendering.Asynchronous Loading: When expanding a node lacking children, the component triggers the
loadDatafunction, allowing fetching children dynamically. While loading, a spinner replaces the arrow icon.Recursive Rendering: The
renderNodesfunction renders nodes recursively based on parent-child relationships.Controlled Selection: The component is controlled via
valueandonChangeprops, allowing integration with form states or external state management.Accessibility: Uses
<ul>,<li>, and buttons with appropriate interaction handlers, although ARIA roles are not explicitly added here.Localization: Utilizes
useTranslationhook for the placeholder text ("Please select").
Interaction with Other Parts of the System
UI Components: Integrates with internal UI components such as
Popover,PopoverTrigger,PopoverContent, andButtonfrom the project’s UI library.Utility Functions: Uses
cnfor conditional classNames andisEmptyfrom lodash for checking empty arrays.Icons: Uses SVG icons from
lucide-reactfor expand/collapse arrows and loading spinner.Internationalization: Depends on the i18next
useTranslationhook for localized placeholder text.Parent Components: Expects the parent to manage and provide updated tree data and handle async loading logic via
loadData.
Diagram: Component Structure and Interaction
componentDiagram
AsyncTreeSelect <|-- Popover
AsyncTreeSelect <|-- PopoverTrigger
AsyncTreeSelect <|-- PopoverContent
AsyncTreeSelect o-- Button : expand/collapse toggles
AsyncTreeSelect o-- "TreeNodeType[]" : treeData
AsyncTreeSelect o-- loadData : async loading callback
AsyncTreeSelect o-- onChange : selection callback
Popover <|-- PopoverTrigger
Popover <|-- PopoverContent
note right of AsyncTreeSelect
- Maintains state: open, expandedKeys, loadingId
- Renders nested <ul> with <li> nodes recursively
- Controls selection & expansion
end note
Summary
The async-tree-select.tsx file provides a reusable and extensible React component that renders a dropdown tree selector with asynchronous loading capabilities. It supports dynamic data fetching for child nodes, recursive rendering of hierarchical data, and user-friendly selection and expansion interactions. The component relies on a flat tree data structure and expects the parent to handle data loading and state updates. It integrates seamlessly with the project’s UI library and translation framework, making it a valuable component for applications needing hierarchical selection with lazy loading.
End of documentation for async-tree-select.tsx