Highly Customizable React Custom Select Box Component

RMAG news

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>

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’);
}
}
}

Let’s begin to handle the functionality.

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: }

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)

here, I’m using the useDebounce hook to handle the search input value

const _query = useDebounce(query, 150)

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])

use these functions to handle the select box values

const handleValueChange = (option: SelectOption) => {
setValue(option)
setQuery()
setIsOpen(false)
}

const handleFocus = () => {
setIsOpen(true)
}

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)

use this wrapperRef as a ref for the wrapper div element

<div ref={wrapperRef} className={styles.wrapper}>
…….
</div>

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

Please follow and like us:
Pin Share