Highly Customizable React Custom Select Box Component

Highly Customizable React Custom Select Box Component

Enhance your user interfaces with a fully customizable React Select Box component. This versatile component replaces standard HTML select boxes with a user-friendly drop-down menu, giving you complete control over style, behavior, and functionality. Build intuitive and interactive forms that seamlessly integrate into your React applications, providing a superior user experience.

Let’s get started!

First of all, we must create the HTML layout of our custom select box, Create Select.tsx file, and put this HTML into the react component return statement

<div className={styles.wrapper}>
<div className={styles.selectedContainer}> {!isOpen && <span className={styles.valueContainer}>{value.label}</span>} <input title='"Select' role='combobox' ref={inputRef} className={styles.inputContainer} type='text' value={query} onChange={(e) => { setQuery(e.target.value) }} placeholder={!value?.label ? placeholder : isOpen ? placeholder : ""} readOnly={!isOpen} onFocus={handleFocus} />
</div> {isOpen && <div className={[styles.optionsList, styles.top].join(' ')} > {filteredOptions.map((option) => ( <div key={option.value} className={styles.option} onClick={handleValueChange.bind(null, option)} > {option.value === value.value && <IoCheckmark /> } {option.value !== value.value && <div className={styles.emptyIcon} /> } <span>{option.label}</span> </div> ))} </div>
} </div>
Enter fullscreen mode
Exit fullscreen mode

Create Selecta .module.scss file and put all these styles into the file

.wrapper { position: relative; border: 1px solid $ash-light; min-width: toRem(200); border-radius: map-get($map: $border-radius, $key: 'md'); background-color: white; cursor: pointer;
} .open { border-color: $primary; input { cursor: default !important; }
} .selectedContainer { position: relative; display: flex; align-items: center; justify-content: center; .inputContainer { position: relative; padding: map-get($map: $padding, $key: 'md'); font-size: map-get($map: $font-size, $key: 'md'); box-sizing: border-box; cursor: default; z-index: 1; background-color: transparent; border: none; outline: none; flex: 1; width: 100%; padding-right: 25px; cursor: pointer; } .valueContainer { width: 100%; position: absolute; font-size: map-get($map: $font-size, $key: 'md'); right: 0; left: map-get($map: $padding, $key: 'md'); z-index: 0; pointer-events: none; } .expandIcon { padding: map-get($map: $padding, $key: 'md'); border-left: 1px solid $ash-light; display: flex; align-items: center; height: 100%; color: $ash-dark; } .clearIcon { padding: map-get($map: $padding, $key: 'md'); position: absolute; right: 35px; height: 100%; display: flex; align-items: center; color: $ash-dark; z-index: 1; }
} .optionsList { position: absolute; margin-top: map-get($map: $margin, $key: 'sm'); border-radius: map-get($map: $border-radius, $key: 'sm'); border: 1px solid $ash-light; width: 100%; padding: calc(map-get($map: $padding, $key: 'md')/3); box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px; // max-height: 200px; // overflow: auto; &.top { top: 100%; } .option { padding: toRem(8) map-get($map: $padding, $key: 'sm'); padding-left: map-get($map: $padding, $key: 'md'); display: flex; align-items: center; cursor: default; font-size: map-get($map: $font-size, $key: 'md'); border-radius: map-get($map: $border-radius, $key: 'sm'); &:hover { background-color: lighten($color: $ash-light, $amount: 7%); } .emptyIcon { width: 14px; height: 14px; } span { margin-left: map-get($map: $margin, $key: 'sm'); } }
}
Enter fullscreen mode
Exit fullscreen mode

Let’s begin to handle the functionality.

  1. extracts the props on top of the component and creates selected value variable
const { defaultValue, options, placeholder, customStyles, clearable } = props; const selected: SelectOption = options.find(opt => opt.value === defaultValue) ?? { label: '', value: '' }
Enter fullscreen mode
Exit fullscreen mode
  1. Create react states and refs
 const [value, setValue] = useState<SelectOption>(selected) const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>(options ?? []) const [isOpen, setIsOpen] = useState<boolean>(false) const [query, setQuery] = useState<string>('') const inputRef = useRef<HTMLInputElement>(null)
Enter fullscreen mode
Exit fullscreen mode
  1. here, I’m using the useDebounce hook to handle the search input value
const _query = useDebounce(query, 150)
Enter fullscreen mode
Exit fullscreen mode
  1. Create useEffect to filter the options based on the input value
 useEffect(() => { if (!_query) { setFilteredOptions(options) return } const regex = new RegExp(_query.toLowerCase(), 'g') const filtered = options.filter(opt => opt.label.toLowerCase().match(regex) ?? opt.value.toLowerCase().match(regex)) setFilteredOptions(filtered) }, [_query, options])
Enter fullscreen mode
Exit fullscreen mode
  1. use these functions to handle the select box values
 const handleValueChange = (option: SelectOption) => { setValue(option) setQuery('') setIsOpen(false) } const handleFocus = () => { setIsOpen(true) }
Enter fullscreen mode
Exit fullscreen mode
  1. To close the dropdown when clicking outside of the component, I’m using the useClickOutside hook as follows
 const handleClickOutside = () => { setIsOpen(false) } const wrapperRef = useClickOutside<HTMLDivElement | null>(handleClickOutside)
Enter fullscreen mode
Exit fullscreen mode

use this wrapperRef as a ref for the wrapper div element

<div ref={wrapperRef} className={styles.wrapper}>
.......
</div>
Enter fullscreen mode
Exit fullscreen mode

The demo code is as follows:

Follow the above stackblitz demo to get a better understanding of that component. It has used framer-motion to animate the drop-down and simplebar-react used as a scrollbar and also react-icons


Discover more from Coursity

Subscribe to get the latest posts sent to your email.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply

    Your email address will not be published. Required fields are marked *