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)
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.
2
Answers
I resolved this issue by removing the value and onChange attribute from Listbox.
Then adding the onClick event handler directly to the input element
Because
onChange
handler will be called with an array containing all selected options any time an option is added or removed.just need set selected make = inputs without stripped out with inp[0]