reactjstypescriptnext.jstailwind-cssheadless-ui

React state gets reset when I try to update it


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

enter image description here

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

enter image description here

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.


Solution

  • 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)}/>