Creating a Custom Select Component with Tree View in React

ashmunir

Ayesha Munir

Posted on March 17, 2024

Creating a Custom Select Component with Tree View in React

In this blog post, we'll walk through the process of creating a custom select component in React that includes a tree view for displaying options and sub-options. šŸš€

Custom Select Component šŸŽ›ļø
The custom select component is a dropdown menu that allows users to select an option from a list. Here's the basic structure of our custom select component:

import PropTypes from 'prop-types'
import TreeView from './treeView'
import React, { useState, useEffect, useRef } from 'react'
import { iconExtraSmall } from '@/lib/utils/helperUtils/constants'
import { IconChevronDown, IconChevronUp } from '@tabler/icons-react'

const CustomSelect = ({ options, selectedOption, setSelectedOption }) => {
    const dropdownRef = useRef(null)
    const [expanded, setExpanded] = useState({})
    const [isOpen, setIsOpen] = useState(false)
    const [selectedOptionObject, setSelectedOptionObject] = useState(null)

    useEffect(() => {
        const handleClickOutside = (event) => {
            if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
                setIsOpen(false)
            }
        }

        document.addEventListener('mousedown', handleClickOutside)
        return () => {
            document.removeEventListener('mousedown', handleClickOutside)
        }
    }, [])

    const handleExpand = (id) => {
        setExpanded((prev) => ({ ...prev, [id]: !prev[id] }))
    }

    const handleSelect = (option) => {
        setSelectedOption(option.id)
        setSelectedOptionObject(option)
    }

    return (
        <div className="relative" ref={dropdownRef}>
            <div className="w-full text-xs font-normal h-[40px] text-fieldsText rounded-md outline-none border-input bg-fieldsBg  z-50 max-h-96 min-w-[8rem] overflow-hidden">
                <button
                    className="flex h-10 w-full items-center whitespace-nowrap rounded-md bg-transparent px-3 py-2 text-xs shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1"
                    onClick={() => setIsOpen(!isOpen)}
                    disabled={!options?.length}
                    type="button"
                >
                    {selectedOption && selectedOptionObject?.name}
                    <div className="ml-auto">
                        {isOpen ? (
                            <IconChevronUp size={iconExtraSmall} className={'cursor-pointer'} />
                        ) : (
                            <IconChevronDown size={iconExtraSmall} className={'cursor-pointer'} />
                        )}
                    </div>
                </button>
            </div>
            {isOpen && (
                <div className="absolute top-full w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-50 p-1 overflow-auto max-h-36">
                    {options?.map((option) => (
                        <TreeView
                            key={option.id}
                            option={option}
                            handleExpand={handleExpand}
                            handleSelect={handleSelect}
                            expanded={expanded}
                            selectedOption={selectedOption}
                        />
                    ))}
                </div>
            )}
        </div>
    )
}

CustomSelect.propTypes = {
    options: PropTypes.array,
    selectedOption: PropTypes.string,
    setSelectedOption: PropTypes.func,
}

export default CustomSelect
Enter fullscreen mode Exit fullscreen mode

Here you can see Select field with Chevron down icon on right. By clicking on field it will expand the dropdown and you will be able to see main options in one go.

Custom Select Component


Tree View Component
The tree view component is a visual representation of hierarchical data. It displays data as a tree-like structure with collapsible nodes. This is particularly useful when dealing with nested data, like a directory of folders and files or a nested menu structure.

import PropTypes from 'prop-types'

const TreeView = ({ option, handleExpand, handleSelect, expanded, selectedOption, level = 0 }) => {
    const getIcon = (option) => {
        if (option.subFolders.length > 0) {
            return (
                <div>
                    <button onClick={() => handleExpand(option.id)} className="flex cursor-default items-center justify-center">
                        <div className="flex items-center justify-center w-3 h-3 border border-fieldsText text-fieldsText text-xs">
                            {expanded[option.id] ? '-' : '+'}
                        </div>
                    </button>
                </div>
            )
        } else {
            return (
                <div className="flex items-center justify-center w-3 h-3 border border-fieldsText text-fieldsText text-[0.6rem] ">
                    x
                </div>
            )
        }
    }

    return (
        <div key={option.id} className="w-full">
            <button
                type="button"
                onClick={() => handleSelect(option)}
                style={{ marginLeft: `${level * 1.25}rem`, width: `calc(100% - ${level * 1.25}rem)` }}
                className={`relative flex py-2 px-1.5  cursor-default text-fieldsText select-none items-center rounded-sm  text-xs outline-none font-normal ${option === selectedOption ? 'bg-accent text-accent-foreground' : ''} hover:bg-accent focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 whitespace-nowrap`}
            >
                <div className="flex items-center">
                    <div className="pr-1.5">{getIcon(option)}</div>
                    {option.name}
                </div>
            </button>
            {expanded[option.id] &&
                option.subFolders.map((subOption) => (
                    <TreeView
                        key={subOption.id}
                        option={subOption}
                        handleExpand={handleExpand}
                        handleSelect={handleSelect}
                        expanded={expanded}
                        selectedOption={selectedOption}
                        level={level + 1}
                    />
                ))}
        </div>
    )
}

TreeView.propTypes = {
    option: PropTypes.object,
    handleExpand: PropTypes.func,
    handleSelect: PropTypes.func,
    expanded: PropTypes.object,
    selectedOption: PropTypes.string,
    level: PropTypes.number,
}

export default TreeView
Enter fullscreen mode Exit fullscreen mode

In the tree view component, It allows users to interact with hierarchical data. It uses '+' and '-' icons to represent expandable and collapsible options respectively. Options without sub-options are represented by an 'x' icon.

Tree view Component

This is Complete view of Select custom component and Tree view for showing and selecting from main options or sub options.

In summary, the tree view component in React effectively displays hierarchical data, offering a user-friendly interface for navigation and selection, and enhancing interactivity with its expand/collapse features. šŸŽ‰


If you have any thoughts or queries, don't hesitate to share them in the comments section. Your input is greatly appreciated! šŸ˜Š

If you found this post helpful, please consider giving it a thumbs up or a share to help others discover it.

Keep coding and stay curious! šŸŽˆ

šŸŒ Get in touch: Ayesha Munir
šŸ‘„ Connect: Linkedin | Facebook| Instagram

šŸ’– šŸ’Ŗ šŸ™… šŸš©
ashmunir
Ayesha Munir

Posted on March 17, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related