[Select] Trigger button value is empty #3798
-
PrefaceI am customizing the AlignUI component Select, which use RadixUI. I wanted to add a searching feature and used Fuse.js to handle fuzzy searching. ProblemAs I type, the input keeps getting unfocused. I learned more and found out that RadixUI has a Typeahead feature for the Select component. I tried to use an inputRef to pull back focus when input onBlur, which works well, but I see the value in trigger disappear when typeahead is "matched". QuestionWhat caused this error? If it is typeahead, is there an option to disable it? Or is there any other work around for this problem? Notes:
Example 1
Example 2
Note: You can see the select's state value on the in edge of the images, 'Enterprise'. Which doesn't change when i capture screenshots for this discussion. Codeconst SearchContent = React.forwardRef<
React.ComponentRef<typeof SelectPrimitives.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitives.Content> & {
items: string[];
placeholder?: string;
onValueChange: (value: string) => void;
}
>(
(
{
className,
position = 'popper',
sideOffset = 8,
collisionPadding = 8,
items,
placeholder,
onValueChange,
...rest
},
forwardedRef
) => {
const inputRef = useInputContext();
const [query, setQuery] = React.useState('');
const fuse = React.useMemo(() => {
return new Fuse(items);
}, [items]);
const results = React.useMemo(() => {
return fuse.search(query).map((i) => i.item);
}, [query, fuse]);
const handleBlur = () => {
inputRef.current?.focus();
};
return (
<SelectPrimitives.Portal>
<SelectPrimitives.Content
ref={forwardedRef}
className={cn(
// base
'relative z-50 overflow-hidden rounded-2xl bg-bg-white-0 shadow-regular-md ring-1 ring-inset ring-stroke-soft-200',
// widths
'min-w-[--radix-select-trigger-width] max-w-[max(var(--radix-select-trigger-width),320px)]',
// heights
'max-h-[--radix-select-content-available-height]',
// animation
'data-[state=open]:animate-in data-[state=open]:fade-in-0',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
'data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
sideOffset={sideOffset}
position={position}
collisionPadding={collisionPadding}
{...rest}
>
<div className='flex w-[228px] py-[12px] px-[14px] border-b-[1px] border-b-stroke-soft-200 gap-[8px]'>
<RiSearch2Line size='16' className='text-text-soft-400' />
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
ref={inputRef}
onBlur={handleBlur}
onKeyUp={(e) => e.key == 'Enter' && onValueChange(results[0])}
placeholder={placeholder ?? 'Search...'}
className='text-label-xs placeholder:text-text-soft-400 text-text-sub-600 outline-none'
/>
</div>
<ScrollAreaPrimitives.Root type='auto'>
<SelectPrimitives.Viewport asChild>
<ScrollAreaPrimitives.Viewport
style={{ overflowY: undefined }}
className='max-h-[196px] w-full scroll-py-2 overflow-auto p-2 pr-[16px]'
>
{query.length
? results.map((item, i) => (
<SelectItem key={i} value={item} className='text-label-xs text-text-sub-600'>
{item}
</SelectItem>
))
: items.map((item, i) => (
<SelectItem key={i} value={item} className='text-label-xs text-text-sub-600'>
{item}
</SelectItem>
))}
</ScrollAreaPrimitives.Viewport>
</SelectPrimitives.Viewport>
<ScrollAreaPrimitives.Scrollbar
orientation='vertical'
className='py-[16px] px-[6px] border-l-[1px] border-l-stroke-soft-200'
>
<ScrollAreaPrimitives.Thumb className='!w-1 rounded bg-bg-soft-200' />
</ScrollAreaPrimitives.Scrollbar>
</ScrollAreaPrimitives.Root>
</SelectPrimitives.Content>
</SelectPrimitives.Portal>
);
}
);You can view the whole code here Usage: <Select.Root value={filter2 ?? ''} onValueChange={setFilter2}>
<Select.Trigger className='min-h-0 h-[36px]'>
<Select.Value placeholder='Segments' />
</SelectSearch.Trigger>
<SelectSearch.Search
items={filterOptions.map((opt) => opt.value)}
placeholder='Search segments...'
onValueChange={setFilter2}
></SelectSearch.Search>
</SelectSearch.Root> |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
The issue is event bubbling. When you type in your search input, the keydown event bubbles up to SelectContent, which has built-in typeahead handling. It intercepts every single character you type and tries to match items. The value disappearing is a separate but related issue. Radix Select uses a portal to display the selected item's text in the trigger. When your search filters out the currently selected item from the DOM, there's nothing left to portal, so the trigger goes blank. The fix is pretty simple. Just stop the event from bubbling: For the disappearing value, pass children directly to SelectValue instead of relying on the portal: When SelectValue has children, it skips the portal logic entirely and just renders what you give it. |
Beta Was this translation helpful? Give feedback.




The issue is event bubbling. When you type in your search input, the keydown event bubbles up to SelectContent, which has built-in typeahead handling. It intercepts every single character you type and tries to match items.
The value disappearing is a separate but related issue. Radix Select uses a portal to display the selected item's text in the trigger. When your search filters out the currently selected item from the DOM, there's nothing left to portal, so the trigger goes blank.
The fix is pretty simple. Just stop the event from bubbling:
For the disappearing value, pass ch…