I have a nextjs application that uses shadcn-ui
combobox component and there is a value that should return back to an empty string after modifying it with useState
, but it does not. I do not understand why. Here is the code:
'use client';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/app/lib/utils';
import { ChevronsUpDown, Check } from 'lucide-react';
import { CrossCircledIcon } from '@radix-ui/react-icons';
import * as React from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
type ComboboxProps = {
options: string[];
searchPlaceholder?: string;
paramsUpdate: string;
height_button?: string;
width_popover?: string;
};
export function Combobox({
options,
searchPlaceholder = 'Search items...',
paramsUpdate,
height_button,
width_popover,
}: ComboboxProps) {
const [open, setOpen] = React.useState<boolean>(false);
const searchParams = useSearchParams();
const [selectedOption, setSelectedOption] = React.useState<string>(
searchParams.get(paramsUpdate) || '',
);
const { replace } = useRouter();
const pathname = usePathname();
const updateURLParams = (q: string) => {
const params = new URLSearchParams(searchParams);
params.set('page', '1');
if (q) {
params.set(paramsUpdate, q);
} else {
params.delete(paramsUpdate);
}
replace(`${pathname}?${params.toString()}`);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger className="flex" asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
'flex-none items-center justify-between text-wrap',
height_button,
)}
>
{selectedOption
? options.find((option) => option === selectedOption)
: searchPlaceholder}
<div className="flex space-x-2">
<CrossCircledIcon
className="ml-2 h-4 w-4 shrink-0 opacity-50 hover:opacity-100"
onClick={() => {
setSelectedOption('');
updateURLParams('');
}}
/>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</div>
</Button>
</PopoverTrigger>
<PopoverContent className={cn('p-0', width_popover)}>
<Command>
<CommandInput placeholder="Search ..." />
<CommandList>
<CommandEmpty>Not found</CommandEmpty>
<CommandGroup>
{options.map((opt, i) => (
<CommandItem
key={opt}
value={opt}
onSelect={(value) => {
setSelectedOption(value === selectedOption ? '' : value);
console.log(value);
updateURLParams(value);
setOpen(false);
}}
className={cn(
i !== options.length - 1 && 'border-b border-gray-200',
)}
>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedOption === opt ? 'opacity-100' : 'opacity-0',
)}
/>
{opt}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
The most important part is this:
<CommandItem
key={opt}
value={opt}
onSelect={(value) => {
setSelectedOption(value === selectedOption ? '' : value);
console.log(value);
updateURLParams(value);
setOpen(false);
}}
In this case value does not change to empty string when I click second time in the same item, I can see that the ui changes to the searchPlaceholder
but console.log(value)
still gives me the previous value, why ?
You need to set it to an empty string instead of the latest value which is the same as the last one
onSelect={(value) => {
let newVal = value === selectedOption ? '' : value;
setSelectedOption(newVal);
console.log(newVal);
updateURLParams(newVal);
setOpen(false);
}}