I am trying to use Listbox from Headless UI to create a select dropdown menu as a filtering criteria for my application (see image). The problem is that when I update my "selectedMake" state, it resets to whatever I initialize it as (I initialized it to ["test"] and in the console it showed ["test"] each time).
So when I select the top 3 items in a row this is what gets outputted to my terminal for selectedMake:
['test'] (initial page render)
['test', 'Hino'] <-- first selection
['test']
['test', 'Bollinger']
['test']
['test', 'Isuzu']
['test']
Here is the code for my Listbox:
<Listbox value={make} onChange={(value) => handleSelect(value)} multiple>
<div className="relative">
<Listbox.Button
className={`w-[150px] flex space-between items-center border border-gray-300 rounded-sm pl-2 pr-1 py-[2.5px] focus:ring-[1.2px] focus:ring-tsgreen focus:outline-none ${make ? "text-gray-600" : "text-gray-400"}`}
onClick={() => setIsOpen(prev => !prev)}
>
{selectedMake.length > 0 ? (
`${selectedMake[0]} (${selectedMake.length})`
) : "Make"}
<FaChevronDown
size={10}
className="ml-auto text-gray-600"
/>
</Listbox.Button>
{isOpen && (
<Listbox.Options static className="absolute w-full border border-gray-300 bg-white text-gray-600">
{make && (
<button
className="relative cursor-pointer pl-7 py-[2px] text-gray-900 hover:font-medium"
onClick={() => setIsOpen(false)}
>
<span>Clear</span>
<span className="absolute inset-y-0 pl-1 left-0 top-0 flex items-center text-black">
<RiCloseFill size={22} />
</span>
</button>
)}
{makes.map((make, i) => (
<Listbox.Option
key={i}
className={({ active }) =>
`relative cursor-pointer pl-2 py-[2px] ${active ? 'bg-lime-200/70 text-lime-900' : 'text-gray-900'}`
}
value={make}
>
{({ selected }) => (
<>
<label className={`flex items-center space-x-2 py-[2px] ${false ? "bg-[#1e90ff] text-white" : ""}`}>
<input
type="checkbox"
className="w-3 h-3 bg-white text-lime-600 rounded-sm border focus:ring-0 focus:ring-offset-0 focus:outline-0"
/>
<span className="flex-grow text-[15px]">{make}</span>
</label>
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
)}
</div>
</Listbox>
Here is my handleSelect function:
const handleSelect = (inp: string[]) => {
const input = inp[0];
setSelectedMake(prev => {
if (prev.includes(input)) {
return prev.filter(m => m !== input);
} else {
return [...prev, input];
}
});
};
Here is the state being used and the constant "makes":
const [isOpen, setIsOpen] = useState(false)
const [make, setMake] = useState<string[]>([]);
const [selectedMake, setSelectedMake] = useState<string[]>(["test]);
const makes = ["Hino", "Bollinger", "Isuzu", "Heil", "Battle Motors"]
The "selectedMake" state isn't even necessary, with Headless UI these to lines below essentially do the same thing. But I added "selectedMake" just for debugging purposes and I got the same result.
<Listbox value={make} onChange={/* Manually updating the "make" state */} multiple>
<Listbox value={make} onChange={setMake} multiple>
Here's my console.log statement:
useEffect(() => {
console.log(selectedMake);
}, [selectedMake])
I thank you for your time on this bug.
I have been stuck on this problem all day, and I have tried my best to fix it but to no avail. Is it possible that the issue lies with Headless UI? I'm using NEXT.js, typescript, tailwind and Headless UI.
I resolved this issue by removing the value and onChange attribute from Listbox.
<Listbox multiple>
Then adding the onClick event handler directly to the input element
<input
type="checkbox"
className="w-3 h-3 bg-white text-lime-600 rounded-sm border focus:ring-0
focus:ring-offset-0 focus:outline-0"
onClick={() => handleSelect(make)}/>