reactjsinputcheckboxdropdown

Multiselect dropdown doesn't keep the right input checked


I'm building a multiselect dropdown with a field to search for options. It's working perfectly except for one aspect:

1 - Here I just selected the options. The first input shows the state of the variable:

Choosing options

2 - Here I searched for values with the digit "4". The option "4" is already selected, but I didn't do this, I selected options "1" and "3":

Searching for options

Here's is the components' code (I removed the parts that doesn't matter about this issue like tailwind notations and functions that is not used by the filter logic):

"use client"
import React, { useEffect, useId, useState } from "react"

export default function Cscheckdropdown(props: Props) {

    const inputText = useId()
    const inputSearch = useId()

    const [value, setValue] = useState < string[] > ([])
    const [searchValue, setSearchValue] = useState("")
    const [optionsFiltered, setOptionsFiltered] = useState(props.options.map((option) => { return option }))

    const handleSetValue = (e: React.MouseEvent<HTMLInputElement>) => {
        const valor = e.currentTarget.value

        if (!value.includes(valor)) {
            setValue([...value, valor])
        }
        else if (value.includes(valor)) {
            setValue(value.filter(v => v != valor))
        }
    }

    useEffect(() => {
        setOptionsFiltered(
            props.options.filter((x) => {
                if (x.includes((searchValue))) {
                    return x
                }
            })
        )
    }, [props.options, searchValue])

    return (<>
        <div>
            <input id={inputSearch} name={props.name + "_inputSearch"} type="text" onChange={(e) => { setSearchValue(e.currentTarget.value) }} value={searchValue} />
        </div>
        {
            optionsFiltered.map((option, index) => {
                return (
                    <div key={index}>
                        <label htmlFor={inputText + index}>
                            <input type="checkbox" id={inputText + index} name={props.name} value={option} onClick={handleSetValue} />
                            {option}
                        </label>
                    </div>
                )
            })
        }
    </>
    )
}


Solution

  • Your checkbox element has no checked attribute. I was expecting to see something like checked={value.includes(option)}. It looks like you're relying on the DOM saving the selection; it needs to be state driven.

    Further, option makes a better key than than index. That's probably why you're seeing the first option remaining selected, even after filtering.

    Finally, your optionsFiltered variable would be better written as a memo, rather than a state variable with an effect hook. i.e.

    const optionsFiltered = useMemo(() => {
        return props.options.filter((opt) => {
            return opt.includes(searchValue);
        });
    }, [props.options, searchValue]);